(转)多线程中共享资源访问中的互斥变量加锁操作函数

EnterCriticalSection

百科名片

多个线程操作相同的数据时,一般是需要按顺序访问的,否则会引导数据错乱,无法控制数据,变成随机变量。为解决这个问题,就需要引入互斥变量,让每个线程都按顺序地访问变量。这样就需要使用EnterCriticalSectionLeaveCriticalSection函数。

目录

函数原型

访问机制

 

编辑本段函数原型

函数EnterCriticalSection和LeaveCriticalSection声明如下:

WINBASEAPI

VOID

WINAPI

EnterCriticalSection(

__inout LPCRITICAL_SECTION lpCriticalSection

);

是多线程中用来确保同一时刻只有一个线程操作被保护的数据的操作函数,相关的多线程数据操作函数还有:

InitializeCriticalSection(&cs);//初始化临界区

EnterCriticalSection(&cs);//进入临界区

//操作数据

MyMoney*=10;//所有访问MyMoney变量的程序都需要这样写Enter.. Leave...

LeaveCriticalSection(&cs);//离开临界区

DeleteCriticalSection(&cs);//删除临界区

编辑本段访问机制

多个线程操作相同的数据时,一般是需要按顺序访问的,否则会引导数据错乱,无法控制数据,变成随机变量。为解决这个问题,就需要引入互斥变量,让每个线程都按顺序地访问变量。这样就需要使用EnterCriticalSectionLeaveCriticalSection函数。

比如说我们定义了一个共享资源dwTime[100],两个线程ThreadFuncAThreadFuncB都对它进行读写操作。当我们想要保证 dwTime[100]的操作完整性,即不希望写到一半的数据被另一个线程读取,那么用CRITICAL_SECTION来进行线程同步如下:

第一个线程函数:

DWORD WINAPI ThreadFuncA(LPVOID lp)

{

EnterCriticalSection(&cs);

...

// 操作dwTime

...

LeaveCriticalSection(&cs);

return 0;

}

写出这个函数之后,很多初学者都会错误地以为,此时csdwTime进行了锁定操作,dwTime处于cs的保护之中。一个“自然而然”的想法就是——csdwTime一一对应上了。

这么想,就大错特错了。dwTime并没有和任何东西对应,它仍然是任何其它线程都可以访问的。如果你像如下的方式来写第二个线程,那么就会有问题:

DWORD WINAPI ThreadFuncB(LPVOID lp)

{

...

// 操作dwTime

...

return 0;

}

当线程ThreadFuncA执行了EnterCriticalSection(&cs),并开始操作dwTime[100]的时候,线程 ThreadFuncB可能随时醒过来,也开始操作dwTime[100],这样,dwTime[100]中的数据就被破坏了。

为了让CRITICAL_SECTION发挥作用,我们必须在访问dwTime的任何一个地方都加上 EnterCriticalSection(&cs)LeaveCriticalSection(&cs)语句。所以,必须按照下面的 方式来写第二个线程函数:

DWORD WINAPI ThreadFuncB(LPVOID lp)

{

EnterCriticalSection(&cs);

...

// 操作dwTime

...

LeaveCriticalSection(&cs);

return 0;

}

这样,当线程ThreadFuncB醒过来时,它遇到的第一个语句是EnterCriticalSection(&cs),这个语句将对cs变量 进行访问。如果这个时候第一个线程仍然在操作dwTime[100]cs变量中包含的值将告诉第二个线程,已有其它线程占用了cs。因此,第二个线程的 EnterCriticalSection(&cs)语句将不会返回,而处于挂起等待状态。直到第一个线程执行了 LeaveCriticalSection(&cs),第二个线程的EnterCriticalSection(&cs)语句才会返回, 并且继续执行下面的操作。

这个过程实际上是通过限制有且只有一个函数进入CriticalSection变量来实现代码段同步的。简单地说,对于同一个 CRITICAL_SECTION,当一个线程执行了EnterCriticalSection而没有执行LeaveCriticalSection的时 候,其它任何一个线程都无法完全执行EnterCriticalSection而不得不处于等待状态。

再次强调一次,没有任何资源被“锁定”,CRITICAL_SECTION这个东东不是针对于资源的,而是针对于不同线程间的代码段的!我们能够用它来进 行所谓资源的“锁定”,其实是因为我们在任何访问共享资源的地方都加入了EnterCriticalSection和 LeaveCriticalSection语句,使得同一时间只能够有一个线程的代码段访问到该共享资源而已(其它想访问该资源的代码段不得不等待)。

这就是使用一个CRITICAL_SECTION时的情况。你应该要知道,它并没有什么可以同步的资源的“集合”。这个概念不正确。如果是两个CRITICAL_SECTION,就以此类推。[1] 

线程锁的概念函数EnterCriticalSectionLeaveCriticalSection的用法

注:使用结构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);

}

DeleteCriticalSection(&cs);//删除临界区

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

如果把两个线程函数中的EnterCriticalSectionLeaveCriticalSection位置移到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

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

参考资料

1  理解EnterCriticalSection 临界区 .博客园[引用日期2012-12-13]

2  线程锁的概念函数EnterCriticalSectionLeaveCriticalSection的用法 csdn[引用日期2013-02-6]

 

posted @ 2013-09-11 10:39  lihaiping  阅读(648)  评论(0)    收藏  举报