终于找到整个工程的关键部分了:就是
在C++里,线程同步控制主要有四种方式:
1.Critial Sections 临界区域
2.mutex 互斥3.Semaphone 信号量4.事件一.
所谓临界区,就是指一块处理共享资源的代码。这段代码可以用Critial Sections保护起来,确保一次只有一个线程进入该代码即进入临界区。 Critial Sections并不是核心对像,没有Handle,也没有像创建核心对像一样Create函数,只需要初始化一个CRITICAL_SECTION类型的变量。所以Critial Sections相对其他三种同步方法比较简单,但同时也失去了灵活性。 对于Critial Sections,WINDOW API提供了四个函数: 1. VOID InitializeCritialSection(LPCRITICAL_SECTION lpCs); --初如化变量 2. VOID DeleteCritialSection(LPCRITICAL_SECTION lpCs) ---清除变量 3. VOID EnterCritialSection(LPCRITICAL_SECTION lpCs) --进入临界区即加锁 4. VOID LeaveCritialSection(LPCRITICAL_SECTION lpCs) --离开临界区即解锁.下面是一个简单的模拟售票的程序。
1 #include "stdafx.h" 2 #include3 4 LONG g_value = 49; 5 CRITICAL_SECTION cs; 6 7 DWORD WINAPI ThreadFun(LPVOID lpParam){ 8 for (int i=0;i<50;i++) 9 {10 //EnterCriticalSection(&cs);11 if (g_value > 0)12 {13 Sleep(1);14 printf("售出第%d张\n",49-g_value+1);15 g_value--; 16 }17 //LeaveCriticalSection(&cs);18 }19 return 0;20 }21 22 int _tmain(int argc, _TCHAR* argv[])23 {24 InitializeCriticalSection(&cs);25 HANDLE handles[2];26 for (int i=0;i<2;i++)27 {28 handles[i] = CreateThread(NULL,0,ThreadFun,NULL,0,NULL);29 }30 Sleep(100000);//等待以致两个子线程运行完成。31 32 for (int i=0;i<2;i++)33 {34 CloseHandle(handles[i]);35 }36 DeleteCriticalSection(&cs);37 return 0;38 }39
为了让效果更明显,线程函数里加Sleep(1),让其他线程得以执行。
售出第1张
售出第1张售出第3张售出第4张售出第5张售出第6张售出第7张售出第8张售出第9张售出第9张售出第11张售出第12张售出第13张售出第14张售出第15张售出第16张售出第17张售出第18张售出第19张售出第20张售出第21张售出第21张售出第23张售出第24张售出第25张售出第26张售出第27张售出第28张售出第29张售出第30张售出第31张售出第32张售出第33张售出第33张售出第35张售出第36张售出第37张售出第38张售出第39张售出第40张售出第41张售出第42张售出第43张售出第44张售出第45张售出第45张售出第47张售出第48张售出第49张售出第50张可以看出来,这些数据是乱的,甚至多出一张票。当线程1执行到第二行时,判断G_value>0成立,这里sleep,线程2执行,将G_value减为了0.但线程1下一个运行时刻,G_value已经是0。所以才会买到50张票。把10与此17行的注释打开,一切正常。
看到有些网友对CRITICAL_SECTION 理解存在一些误区,认为CRITICAL_SECTION 对g_value进行了锁定.其实不然,如果在其他非临界区内,g_value同样可非同步访问了.所以说CRITICAL_SECTION 锁定的是代码块,而不是具体变量.
我个人认为Java中synchronized与CRITICAL_SECTION 比较相近,我们可以认为
1. VOID InitializeCritialSection(LPCRITICAL_SECTION lpCs); --为变量Cs关联了一把锁
2. VOID DeleteCritialSection(LPCRITICAL_SECTION lpCs) ---清除变量Cs关联的锁
3. VOID EnterCritialSection(LPCRITICAL_SECTION lpCs) --尝试获取变量Cs上锁.获得则进入代码块,否则等待.
4. VOID LeaveCritialSection(LPCRITICAL_SECTION lpCs) --释放获得的CS上的锁.暂不知妥否,请指正.
二.mutrex
Critial Section简单易用。但是正如上面所说,这是以其灵活性对代价的。看下面这个线程函数:
void Swap(Object* first,Objct* second){ EnterCriticalSection(&first->cs); EnterCriticalSection(&second->cs);//do swap LeaveCriticalSection(&second->cs); LeaveCriticalSection(&first->cs);}假如两个线程几乎在同一时间内调用Swap函数。
void Swap(first,second)
void Swap (second,first)当线程1进入第一个Critical Section时,线程发生调度。然后线程2也进入第一个Critical Section。这时就发生死锁。
这是我们想到用WaitForMultiObject函数,要么全获得,要么一个都不要。但又发现Critical Section没有Handle.无处入手。这时我们可以采用mutrex 取而代之。下面是一个简单的对比表:
Critical_Section Mutrex锁住Critical_Section的时间比锁住 Mutrex有Handle,可以有名字。
Mutrex快很多。Critical_Section在用户状执行 Mutrex可以跨进程。 Mutrex在核心态。 InitializeCritialSection CreateMutrex OpenMutrexEnterCritialSection
WaitForSingleObject WaitForMultipleObject MsgWaitForMultipleObject LeaveCritialSection ReleaseMutrex DeleteCritialSection CloseHandle.WIDOW API对Mutrex的操作主要有以下几个函数
1.Handle CreateMutrex(LPSECUEIRY_ATTRIBUTES,BOOL initOwner,LPSTR Name); --创建一个互斥量,如果让当前线程锁住该Mutrex。 initOwner为TRUE,name设定Mutrex的名字。 2.HANDLE OpenMutex(DWORD dwDesiredAccess, BOOL bInheritHandle,LPCTSTR lpName); --打开一个Mutex 3.WaitForSingleObject,WaitForMultipleObject,MsgWaitForMultipleObject 锁定mutrex 4.ReleaseMutex(Handle mutrex) 释放Mutex,线程结束同样会释放Mutex。 上面的例子换成Mutex,消除了死锁的可能性:void Swap(Object* first,Objct* second){
HANDLE hs[2] = HANDLE[]{first->mutrex,second->mutrex}; MsgWaitForMultipleObject(2,hs,true,INFINITE); //do swap ReleaseMutex(hs[0]); ReleaseMutex(hs[1]);}