Windows线程的同步与互斥
系统中的所有线程都必须拥有对各种系统资源的访问权,这些资源包括内存堆栈,串口,文件,窗口和许多其他资源。如果一个线程需要独占对资源的访问权,那么其他线程就无法完成它们的工作。反过来说,也不能让任何一个线程在任何时间都能访问所有的资源。如果在一个线程从内存块中读取数据时,另一个线程却想要将数据写入同一个内存块,那么这就像你在读一本书时另一个人却在修改书中的内容一样。这样,书中的内容就会被搞得乱七八糟,结果什么也看不清楚。
线程需要在下面两种情况下互相进行通信:
1.当有多个线程访问共享资源而不使资源被破坏时。
2.当一个线程需要将某个任务已经完成的情况通知另外一个或多个线程时。
Windows下线程同步互斥常用的几种方法:
1)CriticalSection: 临界区
适用范围: 单一进程的各线程之间用来排它性占有
特性: 不是内核对象,快速而有效。无法监测是否被线程放弃。如果在Critical Sections中间突然程序crash或是exit而没有调用LeaveCriticalSection,则结果是该线程所对应的内核不能被释放,该线程成为死线程。
函数: EnterCriticalSection LeaveCriticalSection
很好的封装:
class CritSect { public: friend class Lock; CritSect() { InitializeCriticalSection(&_critSection); } ~CritSect() { DeleteCriticalSection(&_critSection); } private: void Acquire(){ EnterCriticalSection(&_critSection); } void Release(){ LeaveCriticalSection(&_critSection); } CRITICAL_SECTION _critSection; }; class Lock { public: Lock(CritSect& critSect):_critSect(critSect) { _critSect.Acquire(); } ~Lock(){ _critSect.Release(); } private: CritSect& _critSect; };
调用:CritSect sect; Lock lock(sect);
2)Mutex: 互斥内核对象
适用范围: 不同线程之间用来排它性占有
特性: 核心对象,哪个线程拥有mutex,那么该mutex的ID和此线程的ID一样。
函数: CreateMutex ReleaseMutex
3)Event: 事件内核对象
适用范围: 用来控制对象信号的接收,常与信号系统结合起来
特性: 核心对象,有两种不同类型的事件对象。一种是人工重置的事件,另一种是自动重置的事件。当人工重置的事件得到通知时(signaled),等待该事件的所有线程均变为可调度线程。当一个自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线程。
Microsoft为自动重置的事件定义了应该成功等待的副作用规则,即当线程成功地调用wait函数等待到该对象时,自动重置的事件就会自动重置到未通知状态(nonsignaled)。通常没有必要为自动重置的事件调用ResetEvent()函数,因为系统会自动对事件进行重置。但是,Microsoft没有为人工重置的事件定义成功等待的副作用,所以需要调用ResetEvent()函数将Event设置为未通知状态(nonsignaled)。当调用SetEvent触发Auto-reset的Event条件时,如果没有被条件阻塞的线程,那么此函数仍然起作用,条件变量会处在触发状态(和Linux的pthread_cond_signal()不同)。
函数: CreateEvent OpenEvent PulseEvent SetEvent ResetEvent
4)Semaphore: 信号内核对象
适用范围: 用来限制资源占用
特性: 核心对象,没有拥有者,任何线程都可释放。信号量(Semaphore)内核对象对线程的同步方式与前面几种方法不同,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用 CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过 ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。
函数: CreateSemaphore OpenSemaphore ReleaseSemaphore
等待函数
1)WaitForSingleObject()
等待函数可使线程自愿进入等待状态,直到一个特定的内核对象变为已通知状态为止。
DWORD dw = WaitForSingleObject(hProcess, 5000); switch(dw) { case WAIT_OBJECT_0: // The process terminated. break; case WAIT_TIMEOUT: // The process did not terminate within 5000 milliseconds. break; case WAIT_FAILED: // Bad call to function (invalid handle?) break; }
2)WaitForMultipleObjects()
WaitForMultipleObjects与WaitForSingleObject函数很相似,区别在于它允许调用线程同时查看若干个内核对象的已知状态。WaitForMultipleObjects函数的返回值告诉调用线程,为什么它会被重新调度。可能的返回值是WAIT_FAILED和WAIT_TIMEOUT,这两个值的作用是很清楚的。如果为fWaitAll参数传递TRUE,同时所有对象均变为已通知状态,那么返回值是WAIT_OBJECT_0。如果为fWaitAll传递FALSE,那么一旦任何一个对象变为已通知状态,该函数便返回。在这种情况下,你可能想要知道哪个对象变为已通知状态。返回值是WAIT_OBJECT_0与(WAIT_OBJECT_0+dwCount-1)之间的一个值。换句话说,如果返回值不是WAIT_TIMEOUT,也不是WAIT_FAILED,那么应该从返回值中减去WAIT_OBJECT_0。产生的数字是作为第二个参数传递给WaitForMultipleObjects的句柄数组中的索引。该索引说明哪个对象变为已通知状态。下面是说明这一情况的一些示例代码:
HANDLE h[3]; h[0] = hProcess1; h[1] = hProcess2; h[2] = hProcess3; DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000); switch(dw) { case WAIT_FAILED: // Bad call to function (invalid handle?) break; case WAIT_TIMEOUT: // None of the objects became signaled within 5000 milliseconds. break; case WAIT_OBJECT_0 + 0: // The process identified by h[0] (hProcess1) terminated. break; case WAIT_OBJECT_0 + 1: // The process identified by h[1] (hProcess2) terminated. break; case WAIT_OBJECT_0 + 2: // The process identified by h[2] (hProcess3) terminated. break; }
3)SingleObjectAndWait()
DWORD SingleObjectAndWait(HANDLE hObjectToSignal,HANDLE hObjectToWaitOn,DWORD dwMilliseconds,BOOL fAlertable);
函数用于在单个原子方式的操作中发出关于内核对象的通知并等待另一个内核对象:hObjectToSignal参数必须标识一个互斥对象、信号对象或事件对象。hObjectToWaitOn参数用于标识下列任何一个内核对象:互斥对象、信标、事件、定时器、进程、线程、作业、控制台输入和修改通知。与平常一样,dwMilliseconds参数指明该函数为了等待该对象变为已通知状态,应该等待多长时间,而fAlertable标志则指明线程等待时该线程是否应该能够处理任何已经排队的异步过程调用。
4)MsgWaitForMultipleObjects(Ex)
MsgWaitForMultipleObjects和MsgWaitForMultipleObjectsEx这些函数与WaitForMultipleObjects函数十分相似。差别在于它们允许线程在内核对象变成已通知状态或窗口消息需要调度到调用线程创建的窗口中时被调度。
创建窗口和执行与用户界面相关的任务的线程,应该调用MsgWaitForMultipleObjectsEx函数,而不应该调用WaitForMultipleObjects函数,因为后面这个函数将使线程的用户界面无法对用户作出响应。
Windows下的生产者消费者问题:
#include "StdAfx.h" #include <windows.h> #include <stdio.h> #define BUFFER_SIZE 10 typedef struct Prodcon { int readpos; int writepos; //position for reading and writing int buffer[BUFFER_SIZE]; }Prodcon; bool isOver = false; HANDLE hmutex; HANDLE hfullsemaphore; HANDLE hemptysemaphore; void init(Prodcon * pb) { memset(pb->buffer,0,sizeof(pb->buffer)); pb->readpos = 0; pb->writepos = 0; } //store an integer in the buffer void put(Prodcon* pb,int data) { WaitForSingleObject(hemptysemaphore,INFINITE); WaitForSingleObject(hmutex,INFINITE); pb->buffer[pb->writepos] = data; pb->writepos++; pb->writepos %= BUFFER_SIZE; ReleaseMutex(hmutex); ReleaseSemaphore(hfullsemaphore,1,NULL); } //read an integer from the buffer int get(Prodcon* pb) { int data; WaitForSingleObject(hfullsemaphore,INFINITE); WaitForSingleObject(hmutex,INFINITE); data = pb->buffer[pb->readpos]; pb->readpos ++; pb->readpos %= BUFFER_SIZE; ReleaseMutex(hmutex); ReleaseSemaphore(hemptysemaphore,1,NULL); return data; } DWORD WINAPI produce(LPVOID lppara) { Prodcon* pb = (Prodcon*)lppara; while(1) { for(int i=1; i<=50; ++i) { put(pb,i); printf("Thread %d put a data: %d\n",GetCurrentThreadId(),i); Sleep(10); //producer is fast } isOver = true; break; } return NULL; } DWORD WINAPI consume(LPVOID lppara) { Prodcon* pb = (Prodcon*)lppara; while(1) { int d = get(pb); printf("Thread %d get data: %d\n",GetCurrentThreadId(),d); if(isOver == true && pb->readpos == pb->writepos) { printf("OVER!\n"); break; } Sleep(100); //consumer is slow } return NULL; } int main() { DWORD writerdata; DWORD readerdata; DWORD readerdata1; Prodcon pb; init(&pb); hmutex = CreateMutex(NULL,false,NULL); //test produce/consume semaphore trigger hfullsemaphore = CreateSemaphore(NULL,0,BUFFER_SIZE,NULL); hemptysemaphore = CreateSemaphore(NULL,BUFFER_SIZE,BUFFER_SIZE,NULL); if(CreateThread(NULL,0,produce,&pb,0,&writerdata)==NULL) return -1; if(CreateThread(NULL,0,consume,&pb,0,&readerdata)==NULL) return -1; if(CreateThread(NULL,0,consume,&pb,0,&readerdata1)==NULL) return -1; char ch; while(1) { ch = getchar(); //press "e" to exit if(ch == 'e') break; } printf("Program ends successfully\n"); CloseHandle(hmutex); CloseHandle(hfullsemaphore); CloseHandle(hemptysemaphore); return 0; }
参考资料:
http://msdn2.microsoft.com/en-us/library/ms686360(VS.85).aspx
http://www.cppblog.com/mzty/archive/2008/07/29/57470.html