C++线程同步
在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) --离开临界区即解锁.
下面是一个简单的模拟售票的程序。
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
为了让效果更明显,线程函数里加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
OpenMutrex
EnterCritialSection
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]);
}
(待续)