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,

WaitForMultipleObjectsEx,

MsgWaitForMultipleObjects,

MsgWaitForMultipleObjectsEx等等)

不可

当拥有锁的线程死亡时

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系列函数来保证一个线程写多个线程读。

Demo程序

#include <Windows.h>
#include <iostream>
using namespace std;
int index = 0;
// 临界区结构对象
CRITICAL_SECTION g_cs;
HANDLE hMutex = NULL;
void changeMe()
{
cout << index++ << endl;
}
void changeMe2()
{
cout << index++ << endl;
}
void changeMe3()
{
cout << index++ << endl;
}
DWORD WINAPI th1(LPVOID lpParameter)
{
while(1)
{
Sleep(1600); //sleep 1.6 s
// 进入临界区
EnterCriticalSection(&g_cs);
// 等待互斥对象通知
//WaitForSingleObject(hMutex, INFINITE);
// 对共享资源进行写入操作
//cout << "a" << index++ << endl;
changeMe();
changeMe2();
changeMe3();
// 释放互斥对象
//ReleaseMutex(hMutex);
// 离开临界区
LeaveCriticalSection(&g_cs);
}
return 0;
}
DWORD WINAPI th2(LPVOID lpParameter)
{
while(1)
{

Sleep(2000); //sleep 2 s
// 进入临界区
EnterCriticalSection(&g_cs); // 等待互斥对象通知
//WaitForSingleObject(hMutex, INFINITE);
//cout << "b" << index++ << endl;
changeMe();
changeMe2();
changeMe3();
// 释放互斥对象
//ReleaseMutex(hMutex); // 离开临界区
LeaveCriticalSection(&g_cs);
}
return 0;
}
int main(int argc, char* argv[])
{
// 创建互斥对象
//hMutex = CreateMutex(NULL, TRUE, NULL);
// 初始化临界区
InitializeCriticalSection(&g_cs);
HANDLE hThread1;
HANDLE hThread2;
hThread1 = CreateThread(NULL, 0, th1, NULL, 0, NULL);
hThread2 = CreateThread(NULL, 0, th2, NULL, 0, NULL);
int k;
cin >> k;
printf("Hello World!\n");
return 0;
}