互斥量(mutex)內核對象用來確保一個線程獨占對一個資源的訪問。
互斥量對象包含一個使用計數、線程ID以及一個遞歸計數。
互斥量與關鍵段的行為完全相同。但是,互斥量是內核對象,而關鍵段是用戶模式下的同步對象。
線程ID用來標識當前占用這個互斥量的是系統中哪兒個線程,遞歸計數表示這個線程占用該互斥量的次數。
他們一般用來對多個線程訪問的同一塊內存進行保護。如果多個線程要同時更新內存塊,那么其中的數據將遭到破壞。互斥量可以確保正在訪問內存塊的任何線程會獨占對內存塊的訪問權,這樣就保證了數據的完整性。
互斥量的使用規則:
1.如果線程ID為0 (無效線程ID),那么該互斥量不為任何線程所占用,它處于觸發狀態。
可以理解為:無人使用,即觸發。
2.如果線程ID為非零值,那么有一個線程已經占用了該互斥量,他處于未觸發狀態。
3.與所有其他內核對象不同,操作系統對互斥量進行了特殊處理,允許他們違反一些常規的規則。
互斥量也是一個內核對象,它用來確保一個線程獨占一個資源的訪問。互斥量與關鍵段的行為非常相似,并且互斥量可以用于不同進程中的線程互斥訪問資源。使用互斥量Mutex主要將用到四個函數。下面是這些函數的原型和使用說明。
第一個 CreateMutex
函數功能:創建互斥量(注意與事件Event的創建函數對比)
函數原型:
HANDLECreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName
);
函數說明:
第一個參數表示安全控制,一般直接傳入NULL。
第二個參數用來確定互斥量的初始擁有者。
如果傳入TRUE表示互斥量對象內部會記錄創建它的線程的線程ID號并將遞歸計數設置為1,由于該線程ID非零,所以互斥量處于未觸發狀態。
如果傳入FALSE,那么互斥量對象內部的線程ID號將設置為NULL,遞歸計數設置為0,這意味互斥量不為任何線程占用,處于觸發狀態。
第三個參數用來設置互斥量的名稱,在多個進程中的線程就是通過名稱來確保它們訪問的是同一個互斥量。
函數訪問值:
成功返回一個表示互斥量的句柄,失敗返回NULL。
第二個打開互斥量
函數原型:
HANDLEOpenMutex(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName //名稱
);
函數說明:
第一個參數表示訪問權限,對互斥量一般傳入MUTEX_ALL_ACCESS。詳細解釋可以查看MSDN文檔。
第二個參數表示互斥量句柄繼承性,一般傳入TRUE即可。
第三個參數表示名稱。某一個進程中的線程創建互斥量后,其它進程中的線程就可以通過這個函數來找到這個互斥量。
函數訪問值:
成功返回一個表示互斥量的句柄,失敗返回NULL。
第三個觸發互斥量
函數原型:
BOOLReleaseMutex (HANDLE hMutex)
函數說明:
訪問互斥資源前應該要調用等待函數,結束訪問時就要調用ReleaseMutex()來表示自己已經結束訪問,其它線程可以開始訪問了。
最后一個清理互斥量
由于互斥量是內核對象,因此使用CloseHandle()就可以(這一點所有內核對象都一樣)。
在創建互斥量之后,為了獲得對被保護資源的訪問權,線程要調用一個等待函數并傳入互斥量的句柄。在內部,等待函數會檢查線程ID是否為0(互斥量處于觸發狀態)。如果為0,那么函數會把線程ID 設為調用線程的線程ID,把遞歸計數設為1,然后讓調用線程繼續運行。
如果等待函數檢測到線程ID不為0(互斥量處于未觸發狀態),那么調用線程將進入等待狀態。當另一個線程將互斥量的線程ID設為0的時候,系統會記得有一個線程正在等待,于是它把線程ID設為正在等待的那個線程的ID,把遞歸計數設為1,使正在等待的線程變成可調度狀態。這些對互斥量內核對象的檢查和修改都是以原子方式進行的。
即,等待函數檢測的是 線程ID, 而不是遞歸計數。
在用來觸發普通內核對象和撤銷觸發普通內核對象的規則中,有一條不適用于互斥量。
假設線程正在等待一個未觸發的互斥量對象,在這種情況下,線程通常會進入等待狀態。但是,
系統會檢查 想要獲得互斥量的線程的線程ID 與 互斥量對象的內部記錄的線程ID 是否相同。
如果線程ID一致,那么系統會讓線程保持可調度狀態——即使該互斥量尚未觸發。(線程所有權,可理解為同步時,無效)
對于系統中的任何其他內核對象來說,我們都找不到這種“異常”的活動,
每次線程成功的等待了一個互斥量,互斥量對象的遞歸計數會遞增,使遞歸計數大于1的唯一途徑就是利用這個異常,讓線程多次等待同一個互斥量。
當目前占有訪問權的線程不再需要訪問資源的時候,它必須調用releasemutex函數來釋放互斥量。
如果線程成功地等待了互斥量對象不止一次,需要調用ReleaseMutex 相同次數,才能使得對象的遞歸計數變成0。當遞歸計數變成0的時候,函數還會將線程ID設為0,這樣就觸發了對象。
以下代碼中均忘記了關閉互斥量,大家寫的時候不要忘記。
下面舉例:
#include <iostream>
#include <windows.h>
#include <process.h>
using namespace std;
const int threadNum = 20;
HANDLE mutex;
volatile int number;
unsigned int __stdcall threadFunc(PVOID pm) {
int th = * (int *)pm;
WaitForSingleObject(mutex, INFINITE);
number++;
ReleaseMutex(mutex);
return 0;
}
int main() {
HANDLE handle[threadNum];
mutex = CreateMutex(NULL, FALSE, NULL);
int i,n=threadNum;
while(n--) {
number = 0;
for(i=0; i<threadNum; i++) {
handle[i] = (HANDLE) _beginthreadex(NULL, 0, threadFunc, (PVOID) &i, 0, NULL);
}
WaitForMultipleObjects(threadNum, handle, TRUE, INFINITE);
cout << "線程個數為" << number << endl;
}
getchar();
return 0;
}
運行結果為:
mutex = CreateMutex(NULL, FALSE, NULL);
表示互斥量創建時,處于觸發狀態
若將FALSE 改為 TRUE,運行效果如下:
則沒有任何顯示,因為創建時 互斥量的線程ID是 主線程的線程ID, 遞歸計數為1,所以子線程處于等待狀態。
下面例舉不適用互斥量的情況:
一、
#include <iostream>
#include <windows.h>
#include <process.h>
using namespace std;
const int threadNum = 40;
HANDLE mutex;
volatile int number;
unsigned int __stdcall threadFunc(PVOID pm) {
int th = * (int *)pm;
Sleep(100);
number++;
Sleep(100);
ReleaseMutex(mutex);
return 0;
}
int main() {
HANDLE handle[threadNum];
mutex = CreateMutex(NULL, FALSE, NULL);
int i,n=threadNum;
while(n--) {
number=0;
for(i=0; i<threadNum; i++) {
handle[i] = (HANDLE) _beginthreadex(NULL, 0, threadFunc, (PVOID) &i, 0, NULL);
WaitForSingleObject(mutex, INFINITE); // 等待函數檢測線程ID
}
WaitForMultipleObjects(threadNum, handle, TRUE, INFINITE);
cout << "線程個數為" << number << endl;
}
getchar();
return 0;
}
運行結果如下:
因為線程所有權的概念,此時子線程之間 不是互斥的關系了。
二、
#include <iostream>
#include <windows.h>
#include <process.h>
using namespace std;
const int threadNum = 10;
HANDLE mutex;
volatile int number;
unsigned int __stdcall threadFunc(PVOID pm) {
int th = * (int *)pm;
number++; //線程不安全
return 0;
}
unsigned int __stdcall lastFunc(PVOID pm) {
WaitForSingleObject(mutex, INFINITE);
cout << "hello world" << endl;
Sleep(100);
ReleaseMutex(mutex);
return 0;
}
int main() {
HANDLE handle[threadNum],last_handle;
mutex = CreateMutex(NULL, FALSE, NULL);
int i,n=threadNum;
for(i=0; i<threadNum; i++) {
handle[i] = (HANDLE) _beginthreadex(NULL, 0, threadFunc, (PVOID) &i, 0, NULL);
WaitForSingleObject(mutex, INFINITE); // 等待函數檢測線程ID
}
WaitForMultipleObjects(threadNum, handle, TRUE, INFINITE);
cout << "線程個數為" << number << endl;
last_handle = (HANDLE) _beginthreadex(NULL, 0, lastFunc, NULL, 0, NULL);
Sleep(1000); //保證在回收mutex前 執行完last_handle,否則互斥量將不起作用
CloseHandle(mutex);
getchar();
return 0;
}
運行效果如下:
由于互斥量未釋放 導致last_handle線程,等待。
正確釋放mutex
在WaitForMultipleObjects(threadNum, handle, TRUE, INFINITE);之前,加入
注意,釋放mutex必須在其占用的線程當中進行釋放,在其他線程中釋放將沒有任何效果。
for(i=0; i<threadNum; i++) { ReleaseMutex(mutex); }
運行效果如下:
正常。