临界区锁 InitializeCriticalSection()

首先,文章主体经过以下两篇文章整理而成:
http://blog.csdn.net/lys07962000/article/details/41707213
http://blog.csdn.net/bytxl/article/details/46536015

一:深入理解CRITICAL_SECTION

概念介绍

临界区是一种防止多个线程同时执行一个特定代码节的机制,这一主题并没有引起太多关注,因而人们未能对其深刻理解。在需要跟踪代码中的多线程处理的性能时,对 Windows 中临界区的深刻理解非常有用。本文深入研究临界区的原理,以揭示在查找死锁和确认性能问题过程中的有用信息。它还包含一个便利的实用工具程序,可以显示所有临界区及其当前状态。

临界区是一种轻量级机制,在某一时间内只允许一个线程执行某个给定代码段。通常在修改全局数据(如集合类)时会使用临界区。事件、多用户终端执行程序和信号量也用于多线程同步,但临界区与它们不同,它并不总是执行向内核模式的控制转换,这一转换成本昂贵。稍后将会看到,要获得一个未占用临界区,事实上只需要对内存做出很少的修改,其速度非常快。只有在尝试获得已占用临界区时,它才会跳至内核模式。这一轻量级特性的缺点在于临界区只能用于对同一进程内的线程进行同步。
临界区由 WINNT.H 中所定义的 RTL_CRITICAL_SECTION 结构表示。因为您的 C++ 代码通常声明一个 CRITICAL_SECTION 类型的变量,所以您可能对此并不了解。研究 WINBASE.H 后您会发现:

//typedef :在计算机编程语言中用来为复杂的声明定义简单的别名
typedef RTL_CRITICAL_SECTION CRITICAL_SECTION;

我们将在短时间内揭示 RTL_CRITICAL_SECTION 结构的实质。此时,重要问题在于 CRITICAL_SECTION(也称作 RTL_CRITICAL_SECTION)只是一个拥有易访问字段的结构,这些字段可以由 KERNEL32 API 操作。

在将临界区传递给 InitializeCriticalSection 时(或者更准确地说,是在传递其地址时),临界区即开始存在。初始化之后,代码即将临界区传递给 EnterCriticalSection 和 LeaveCriticalSection API。一个线程自 EnterCriticalSection 中返回后,所有其他调用 EnterCriticalSection 的线程都将被阻止,直到第一个线程调用 LeaveCriticalSection 为止。最后,当不再需要该临界区时,一种良好的编码习惯是将其传递给DeleteCriticalSection。

临界区锁 InitializeCriticalSection()

此函数初始化一个临界区对象
格式:

void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

参数:lpCriticalSection指向临界区对象的指针。
返回值:无

单个进程的线程可以使用一个互斥同步临界区对象。虽然对线程将获得临界区所有权的顺序没有保证,该系统将处理所有线程的所有权要求。
这个进程负责分配一个临界区对象使用的内存,它可以通过声明类型的CRITICAL_SECTION的变量使用的内存。在使用一个临界区对象以前,一些进程中的线程必须调用InitializeCriticalSection函数来初始化对象。一旦一个临界区对象已被初始化,该进程的线程可以在EnterCriticalSection或LeaveCriticalSection函数指定对象,提供对共享资源的相互独占式访问。对于不同进程之间的类似线程同步,使用互斥对象。一个临界区对象不能移动或复制。这一进程也绝不能修改该对象,但必须把它作为逻辑不透明来处理。只能使用由与Microsoft Win32 ® API提供的临界区功能,用来管理临界区对象。在低内存的情况下,InitializeCriticalSection可能提出STATUS_NO_MEMORY异常。

DeleteCriticalSection

删除关键节对象释放由该对象使用的所有系统资源。

void WINAPI DeleteCriticalSection(_Inout_ LPCRITICAL_SECTION lpCriticalSection);

参数:lpCriticalSection,对关键节对象的指针。先前必须已将该对象初始化与InitializeCriticalSection函数中。

线程锁的概念函数EnterCriticalSection和LeaveCriticalSection

注:使用结构CRITICAL_SECTION 需加入头文件#include”afxmt.h”
定义一个全局的锁 CRITICAL_SECTION的实例和一个静态全局变量

CRITICAL_SECTION cs;//可以理解为锁定一个资源
static int n_AddValue = 0;//定义一个静态的全部变量n_AddValue

创建两个线程函数,代码实现如下:

//第一个线程
UINT FirstThread(LPVOID lParam)
{
    EnterCriticalSection(&cs);//加锁 接下来的代码处理过程中不允许其他线程进行操作,除非遇到LeaveCriticalSection
    for(int i = 0; i<10; i++){       
        n_AddValue ++;
        cout << "n_AddValue in FirstThread is "<<n_AddValue <<endl;       
    }
    LeaveCriticalSection(&cs);//解锁 到EnterCriticalSection之间代码资源已经释放了,其他线程可以进行操作   
    return 0;
}
//第二个线程
UINT SecondThread(LPVOID lParam)
{
    EnterCriticalSection(&cs);//加锁
    for(int i = 0; i<10; i++){       
        n_AddValue ++;       
        cout << "n_AddValue in SecondThread is "<<n_AddValue <<endl;     
    }
    LeaveCriticalSection(&cs);//解锁
    return 0;
}

在主函数添加以下代码

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
    int nRetCode = 0;
    // 初始化 MFC 并在失败时显示错误
    if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
    {
        // TODO: 更改错误代码以符合您的需要
        _tprintf(_T("错误: MFC 初始化失败/n"));
        nRetCode = 1;
    }
    else
    {
        InitializeCriticalSection(&cs);//初始化结构CRITICAL_SECTION
        CWinThread *pFirstThread,*pSecondThread;//存储函数AfxBeginThread返回的CWinThread指针
        pFirstThread  = AfxBeginThread(FirstThread,LPVOID(NULL));//启动第一个线程
        pSecondThread = AfxBeginThread(SecondThread,LPVOID(NULL));//启动第二个线程
        HANDLE hThreadHandle[2];//
        hThreadHandle[0] = pFirstThread->m_hThread;
        hThreadHandle[1] = pSecondThread->m_hThread;
        //等待线程返回
WaitForMultipleObjects(2,hThreadHandle,TRUE,INFINITE);       
    }
    return nRetCode;
}

输出:

n_AddValue in FirstThread is 1
n_AddValue in FirstThread is 2
n_AddValue in FirstThread is 3
n_AddValue in FirstThread is 4
n_AddValue in FirstThread is 5
n_AddValue in FirstThread is 6
n_AddValue in FirstThread is 7
n_AddValue in FirstThread is 8
n_AddValue in FirstThread is 9
n_AddValue in FirstThread is 10
n_AddValue in SecondThread is 11
n_AddValue in SecondThread is 12
n_AddValue in SecondThread is 13
n_AddValue in SecondThread is 14
n_AddValue in SecondThread is 15
n_AddValue in SecondThread is 16
n_AddValue in SecondThread is 17
n_AddValue in SecondThread is 18
n_AddValue in SecondThread is 19
n_AddValue in SecondThread is 20

如果把两个线程函数中的EnterCriticalSection和LeaveCriticalSection位置移到for循环中去,线程的执行顺序将会改变输出也就跟着改变,如:

//第一个线程
UINT FirstThread(LPVOID lParam)
{ 
    for(int i = 0; i<10; i++){
        EnterCriticalSection(&cs);//加锁 锁移到for循环内部里
        n_AddValue ++;
        cout << "n_AddValue in FirstThread is "<<n_AddValue <<endl;   
        LeaveCriticalSection(&cs);//解锁 
    }   
    return 0;
}
//第二个线程
UINT SecondThread(LPVOID lParam)
{   
    for(int i = 0; i<10; i++){   
        EnterCriticalSection(&cs);//加锁
        n_AddValue ++;       
        cout << "n_AddValue in SecondThread is "<<n_AddValue <<endl;
        LeaveCriticalSection(&cs);//解锁       
    }
    return 0;
}

其他代码不变,输出的结果如下:

n_AddValue in FirstThread is 1
n_AddValue in SecondThread is 2
n_AddValue in FirstThread is 3
n_AddValue in SecondThread is 4
n_AddValue in FirstThread is 5
n_AddValue in SecondThread is 6
n_AddValue in FirstThread is 7
n_AddValue in SecondThread is 8
n_AddValue in FirstThread is 9
n_AddValue in SecondThread is 10
n_AddValue in FirstThread is 11
n_AddValue in SecondThread is 12
n_AddValue in FirstThread is 13
n_AddValue in SecondThread is 14
n_AddValue in FirstThread is 15
n_AddValue in SecondThread is 16
n_AddValue in FirstThread is 17
n_AddValue in SecondThread is 18
n_AddValue in FirstThread is 19
n_AddValue in SecondThread is 20

个人认为在函数EnterCriticalSection和LeaveCriticalSection中间的代码执行过程不会被其他线程干拢或者这么讲不允许其他线程中的代码执行。这样可以有效防止一个全局变量在两个线程中同时被操作的可能性。

猜你喜欢

转载自blog.csdn.net/godqiao/article/details/74833696