Mutex 和 Critical Section 都是主要用于限制多线程( Multithread ) 对全局或共享的变量、对象或内存空间的访问。下面是其主要的异同点(不同的地方用绿色表示)。
| Mutex | Critical Section |
性能和速度 | 慢。 Mutex 是内核对象,相关函 数的 执 行 ( WaitForSingleObject , ReleaseMutex )需要 用户模式( User Mode )到内核模式( Kernel Mode )的转换,在 x86 处理器上这种转化一般要发费 600 个左右的 CPU 指令周期。 | 快。 Critical Section 本身不是内核对象,相关函数( EnterCriticalSection , LeaveCriticalSection )的调用一般都在用户模式内执行,在 x86 处理器上一般只需要发费 9 个左右的 CPU 指令周期。只有当想要 获得的锁正好被别的线程拥有时才会退化成和 Mutex 一样,即转换到内核模式,发费 600 个左右的 CPU 指令周期。 |
能否跨越进程( Process )边界 | 可以 | 不可 |
定义写法 | HANDLE hmtx; | CRITICAL_SECTION cs; |
初始化写 法 | hmtx= CreateMutex (NULL, FALSE, NULL); | InitializeCriticalSection(&cs); |
结束清除 写法 | CloseHandle(hmtx); | DeleteCriticalSection(&cs); |
无限期等 待的写法 | WaitForSingleObject (hmtx, INFINITE); | EnterCriticalSection(&cs); |
0 等 待(状态检测)的写法 | WaitForSingleObject (hmtx, 0); | TryEnterCriticalSection(&cs); |
任意时间等待的写法 | WaitForSingleObject (hmtx, dwMilliseconds); | 不支持 |
锁释放的 写法 | ReleaseMutex(hmtx); | LeaveCriticalSection(&cs); |
能否被一道用于等待其他内核对象 | 可以(使用 WaitForMultipleObjects , WaitForMultipleObjects Ex , Msg WaitForMultipleObjects , Msg WaitForMultipleObjects Ex 等等 ) | 不可 |
当拥有锁的线程死亡时 | Mutex 变成 abandoned 状态,其他的等待线程可以获得锁。 | Critical Section 的状态不可知( undefined ),以后的动作就不能保证了。 |
自己会不 会锁住自己 | 不会(对 已获得的 Mutex, 重复调用 WaitForSingleObject 不会锁住自己 。 但最后你别忘了要调用同样次数的 ReleaseMutex ) | 不会(对 已获得的 Critical Section , 重 复调用 EnterCriticalSection 不会锁住自己 。但最后你别忘了要调用同样次数的 LeaveCriticalSection ) |
下面是一些补充:
l 请先检查你的设计,把不必要的全局或共享对象改为局部对象。全局的东西越少,出问题的可能就越小。
l 每次你使用EnterCriticalSection时,请不要忘了在函数的所有可能返回的地方都加上LeaveCriticalSection。对于 Mutex也同样。若你把这个问题和Win32 structured exception或C++ exception一起考虑,你会发现问题并不是那么简单。自定义一个封装类可能是一种解决方案,以Critical Section为例的代码如下所示:
class csholder
{
CRITICAL_SECTION *cs;
public:
csholder(CRITICAL_SECTION *c): cs(c)
{ EnterCriticalSection(cs); }
~csholder() { LeaveCriticalSection(cs); }
};
CRITICAL_SECTION some_cs;
void foo()
{
// ...
csholder hold_some(&some_cs);
// ... CS protected code here
// at return or if an exception happens
// hold_some's destructor is automatically called
}
l 根据你的互斥范围需求的不同,把Mutex或Critical Section定义为类的成员变量,或者静态类变量。
l 若你想限制访问的全局变量只有一个而且类型比较简单(比如是LONG或PVOID型),你也可以使用InterlockedXXX系列函数来保证一个线程 写多个线程读。
本文主要参照了Jeffrey Richter的《Programming Applications for Microsoft Windows, 4th Ed.》。