Windows线程同步
Windows的线程同步可以利用互斥对象来实现,也可以使用事件对象,关键代码段来实现。
1 事件对象实现线程同步
<1>Event对象创建函数
事件对象的创建事件对象属于内核对象,它包含以三个成员:使用计数,是否是自动重置还是人工重置的布尔值,通知状态的布尔值。
HANDLE CreateEvent( LPSECURITY_ATTRIBUTESlpEventAttributes, BOOLbManualReset, BOOLbInitialState, LPCSTRlpName );
bManualReset:TRUE代表是人工重置的事件对象,FALSE代表是自动重置的事件对象。如果是人工重置的事件对象,那么当线程等待到该对象的所有权的时候,需要调用ResetEvent函数将该对象的状态设置为无通知状态,并且所有线程均变为可调度线程;如果是自动重置状态,那么当线程得到该对象的所有权的时候,系统会自动的将该对象设置为无通知状态。
bInitialState:TRUE代表事件对象的初始状态是有信号的,FALSE代表初始状态无信号的。
<2>设置信号的状态
信号的状态可以设置为有信号和无信号两种状态,有信号的设置需要调用函数
BOOL SetEvent(HANDLE hEvent);
设置为无信号需要调用下面的函数:
BOOL ResetEvent(HANDLE hEvent);
<3>利用事件对象实现线程同步的条件
条件就是bManualReset参数必须设置为FALSE,即自动重置的事件对象。
分析:
如果设置bManualReset对象为TRUE的话,此时如果把bInitialState设置为FALSE的话,那么等待事件对象的子线程永远等待不了Event对象变为有信号的状态,因为没有人工重置的话它是不会变成有信号的状态的。
如果把bInitialState设置为TRUE的话,那么子线程都可以去竞争事件的所有权,即便一个线程获得了所有权,但是事件对象是始终有信号的,所以另外的线程可以随时的切换过去,这样也是不可取的。
那么如果在线程获取了对象的所有权后立即ResetEvent将事件对象变成无信号的可行不?答案是不行,因为可能存在这样的情况:就是信号还没来得急改变,时间片已经转到另一个线程了,因为此时的信号是处于通知状态的,那么被切换到的线程也可以获取Event对象的所有权,然后进入它的受保护代码区,这样就造成了不止一个线程同时进入了它们各自的受保护代码区,从而破坏了同步。
结论:
如果想利用Event对象来实现线程同步,那么就必须将bManualReset参数设置为FALSE,即该Event对象时自动重置对象。
示例程序:
#include <iostream> #include <Windows.h> using namespace std; HANDLE gEvent =NULL; int tickets= 100; DWORD WINAPI fun1Proc(LPVOID lpParameter); DWORD WINAPI fun2Proc(LPVOID lpParameter); void main() { HANDLE hThread1 = NULL; HANDLE hThread2 = NULL; //gEvent =CreateEvent(NULL,TRUE,TRUE,NULL); gEvent =CreateEvent(NULL,FALSE,TRUE,NULL); CreateThread(NULL,0,fun1Proc,NULL,0,NULL); CreateThread(NULL,0,fun2Proc,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2); Sleep(4000); CloseHandle(gEvent); } DWORD WINAPI fun1Proc(LPVOID lpParameter) { while(TRUE) { WaitForSingleObject(gEvent,INFINITE); //ResetEvent(gEvent); if(tickets>0 ) { Sleep(1); cout<<"thread 1 is selling tickets:"<<tickets--<<endl; SetEvent(gEvent); } else { SetEvent(gEvent); break; } } return0; } DWORD WINAPI fun2Proc(LPVOID lpParameter) { while(TRUE) { WaitForSingleObject(gEvent,INFINITE); //ResetEvent(gEvent); if(tickets>0 ) { Sleep(1); cout<<"thread 2 is selling tickets:"<<tickets--<<endl; SetEvent(gEvent); } else { SetEvent(gEvent); break; } } return0; }
2 关键代码段
关键代码段也被称为临界区,工作在用户模式下。它是指一个小代码段,在代码能够执行前,它必须独占某些资源的访问权。
它的流程如下所示:
示例代码:
#include <iostream> #include <Windows.h> using namespace std; DWORD WINAPI fun1Proc(LPVOID lpParameter); DWORD WINAPI fun2Proc(LPVOID lpParameter); CRITICAL_SECTIONgCs; int tickets = 100; void main() { HANDLE hThread1 = NULL; HANDLE hThread2 = NULL; hThread1 =CreateThread(NULL,0,fun1Proc,NULL,0,NULL); hThread2 =CreateThread(NULL,0,fun2Proc,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2); InitializeCriticalSection(&gCs); Sleep(4000); DeleteCriticalSection(&gCs); } DWORD WINAPI fun1Proc(LPVOIDlpParameter) { while(TRUE) { EnterCriticalSection(&gCs); //Sleep(1); if(tickets>0) { cout<<"thread1 is selling ticket:"<<tickets--<<endl; LeaveCriticalSection(&gCs); } else { LeaveCriticalSection(&gCs); break; } } return0; } DWORD WINAPI fun2Proc(LPVOIDlpParameter) { while(TRUE) { EnterCriticalSection(&gCs); //Sleep(1); if(tickets>0) { cout<<"thread2 is selling ticket:"<<tickets--<<endl; LeaveCriticalSection(&gCs); } else { LeaveCriticalSection(&gCs); break; } } return0; }
3 三种线程同步方式的比较
<1>Mutex,Event属于内核对象,内核对象在进行线程同步的时候速度较慢
<2>CriticalSection属于用户方式,同步速度慢
<3>Mutex,Event同步的时候需要用到WaitForSingleObject来等待信号变为有效的时候进入核心代码段,等处理完了时候需要将信号设置为有信号状态以便其它线程进入核心代码段。
CriticalSection则是用了比较形象的方式EnterCriticalSection以及LeaveCriticalSection。
<4>Mutex,Event在最终需要CloseHandle来关闭内核对象,CriticalSection则是用DeleteCriticalSection来删除对象。
<5>总体来说三种方式都大同小异,都要用一个标志或者别的方式来宣称自己独占资源,然后在使用完资源的后再释放所有权。