一、前言
之前在项目中,由于需要使用到多线程,多线程能够提高执行的效率,同时也带来线程同步的问题,故特此总结如下。
二、windows线程同步机制
windows线程同步机制常用的有几种:Event、Critical Section、Mutex、Semaphore。
1.Critical Section(临界区)
适用范围: 单一进程的各线程之间用来排它性占有
特性: 不是内核对象,是这里提到的四种机制中速度效率最高的(注意:由于临界区是非内核对象,可在用户空间维护,则无句柄,那么就不能使用WaitForMultipleObjects防止死锁的问题了)。无法监测是否被线程放弃。如果在Critical Sections中间突然程序crash或是exit而没有调用LeaveCriticalSection,则结果是该线程所对应的内核不能被释放,该线程成为死线程。
临界区相关函数:
1 VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection); //函数功能初始化一个临界资源对象。 2 VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection); // 进入临界区 3 VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection); // 离开临界区 4 VOID DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection); // 销毁临界区
2.Mutex(互斥内核对象)
适用范围: 不同线程之间用来排它性占有
特性: 核心对象,有句柄,有拥有者,哪个线程拥有mutex,就占有mutex的所有权,非拥有者执行ReleaseMutex会出错。mutex可以跨进程同步。
互斥量相关函数:
a.创建
1 HANDLE CreateMutex( 2 LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全属性结构指针 3 BOOL bInitialOwner, //是否占有该互斥量 4 LPCTSTR lpName //设置互斥对象的名字 5 );
b.打开一个现有命名互斥量
1 HANDLE OpenMutex( 2 DWORDdwDesiredAccess, // access 3 BOOLbInheritHandle, // inheritance option 4 LPCTSTRlpName // object name 5 );
c.占有/获取互斥量
1 DWORD WaitForSingleObject( 2 HANDLE hHandle, 3 DWORD dwMilliseconds 4 );
d.释放互斥量
1 BOOL WIANPI ReleaseMutex( 2 HANDLE hMutex 3 );
3.Event(事件内核对象)
适用范围: 用来控制对象信号的接收,常与信号系统结合起来
特性: 核心对象,有句柄,有两种不同类型的事件对象。一种是人工重置的事件,另一种是自动重置的事件。当人工重置的事件得到通知时(signaled),等待该事件的所有线程均变为可调度线程,直到调用ResetEvent,事件才会变为unsignaled。当一个自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线程,事件则自动变为unsigned。
SetEvent后,自动置位事件则会保持状态,直到有等待事件的,之后自动复位。手动复位事件,则保存signaled状态直到调用ResetEvent。
PulseEvent后,对于手动复位事件,所有当前线程(如果有的话)会得到释放,而后事件会被复位为unsigned。(不建议使用)
WaitForMultipleObjects,只有当所有等待的事件同时处于signaled,才会释放,对于自动复位的事件在线程释放后自动复位。
相关函数:CreateEvent, OpenEvent,SetEvent, ResetEvent, PulseEvent。
4.Semaphore(信号内核对象)
适用范围: 用来限制资源占用
特性: 核心对象,没有拥有者,任何线程都可释放。信号量(Semaphore)内核对象对线程的同步方式与前面几种方法不同,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用 CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过 ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。
函数: CreateSemaphore OpenSemaphore ReleaseSemaphore
三、对比
Mutex与Critical Section对比
1.Mutex如果被终止线程所泛起,会是已传信的,其他线程不会永远阻塞;而Critical Section则不是,如果被终止线程没有释放,那么其他等待线程将永远阻塞。
2.Mutex是内核对象,,有超时机制(有句柄,可以是使用WaitFor_函数实现);而CS非内核对象,无句柄,不能使用WaitFor系列函数,实现超时机制,只能使用TryEnterCriticalSection轮询。
3.Mutex可命名,可以跨进程同步,CS不行。
4.Mutex可在创建时,立即指定对该mutex的拥有权,CS没有不行。
5.CS由于是非内核对象,不需要频繁的切换程序运行的状态到核心态下,所有CS几乎总是比Mutex要快。
非内核对象(CS)与内核对象(其他的)对比:
1.内核对象有句柄能使用WaitForMultipleObjects,一次等待多个对象,避免死锁;
2.CS无句柄,不能使用,需要自己考虑防止死锁的问题。(给CS编号,按照一定顺序获取,打破形成环)。
3.对于上面几种命名的内核同步对象,可以进行跨进程同步;而,CS是非内核对象,直接建构在用户空间下,速度快的同时带来另一个问题,那就是没有办法跨进程同步。
四、后记
对于CS的使用,我们一般使用内部类的形式,防止异常的时候没有LeaveCriticalSection。
这个可能总结不够全,希望以后能够进行总结。另外该博文参考了http://www.cnblogs.com/luxiaoxun/archive/2012/10/10/2717765.html