C++ 多線程資源鎖的兩種表現形式--Mutex和Critical Section的區別

Mutex和Critical Section都是主要用於限制多線程(Multithread)對全局或共享的變量、對象或內存空間的訪問。下面是其主要的異同點(不同的地方用綠色表示)。


 

Mutex

Critical Section

性能和速度

慢。

Mutex 是內核對象,相關函數的 (WaitForSingleObject

ReleaseMutex)需要用戶模式(User Mode)到內核模式

Kernel Mode)的轉換,在x86處理器上這種轉化一般要

發費600個左右的 CPU指令週期。

快。

Critical Section本身不是內核對象,相關函數

EnterCriticalSectionLeaveCriticalSection

的調用一般都在用戶模式內執行,在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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章