多執行緒同步-互斥物件(深入理解Mutex)
一 Mutex
互斥物件(mutex)核心物件能夠確保執行緒擁有對單個資源的互斥訪問權。實際上互斥物件是因此而得名的。互斥物件包含一個使用數量,一個執行緒ID和一個遞迴計數器。
互斥物件的行為特性與關鍵程式碼段相同,但是互斥物件屬於核心物件,而關鍵程式碼段則屬於使用者方式物件。這意味著互斥物件的執行速度比關鍵程式碼段要慢。但是這也意味著不同程序中的多個執行緒能夠訪問單個互斥物件,並且這意味著執行緒在等待訪問資源時可以設定一個超時值。
ID用於標識系統中的哪個執行緒當前擁有互斥物件,遞迴計數器用於指明該執行緒擁有互斥物件的次數。
互斥物件有許多用途,屬於最常用的核心物件之一。通常來說,它們用於保護由多個執行緒訪問的記憶體塊。如果多個執行緒要同時訪問記憶體塊,記憶體塊中的資料就可能遭到破壞。互斥物件能夠保證訪問記憶體塊的任何執行緒擁有對該記憶體塊的獨佔訪問權,這樣就能夠保證資料的完整性。
互斥物件的使用規則如下:
? 如果執行緒ID是0(這是個無效ID),互斥物件不被任何執行緒所擁有,並且發出該互斥物件的通知訊號。
? 如果ID是個非0數字,那麼一個執行緒就擁有互斥物件,並且不發出該互斥物件的通知訊號。
? 與所有其他核心物件不同, 互斥物件在作業系統中擁有特殊的程式碼,允許它們違反正常的規則。
若要使用互斥物件,必須有一個程序首先呼叫CreateMutex,以便建立互斥物件:
HANDLECreateMutex(
PSECURITY_ATTRIBUTES psa,
BOOL fInitialOwner,
PCTSTR pszName);
InitialOwner引數用於控制互斥物件的初始狀態。如果傳遞FALSE(這是通常情況下傳遞的值),那麼互斥物件的ID和遞迴計數器均被設定為0。這意味著該互斥物件沒有被任何執行緒所擁有,因此要發出它的通知訊號。
如果為fInitialOwner引數傳遞TRUE,那麼該物件的執行緒ID被設定為呼叫執行緒的ID,遞迴計數器被設定為1。由於ID是個非0數字,因此該互斥物件開始時不發出通知訊號。
通過呼叫一個等待函式,並傳遞負責保護資源的互斥物件的控制代碼,執行緒就能夠獲得對共享資源的訪問權。在內部,等待函式要檢查執行緒的ID,以瞭解它是否 是0(互斥物件發出通知訊號)。如果執行緒ID是0,那麼該執行緒ID被設定為呼叫執行緒的ID,遞迴計數器被設定為1,同時,呼叫執行緒保持可排程狀態。
如果等待函式發現ID不是0(不發出互斥物件的通知訊號),那麼呼叫執行緒便進入等待狀態。系統將記住這個情況,並且在互斥物件的ID重新設定為0 時,將執行緒ID設定為等待執行緒的ID,將遞迴計數器設定為1,並且允許等待執行緒再次成為可排程執行緒。與所有情況一樣,對互斥核心物件進行的檢查和修改都是 以原子操作方式進行的。
一旦執行緒成功地等待到一個互斥物件,該執行緒就知道它已經擁有對受保護資源的獨佔訪問權。試圖訪問該資源的任何其他執行緒(通過等待相同的互斥物件)均 被置於等待狀態中。當目前擁有對資源的訪問權的執行緒不再需要它的訪問權時,它必須呼叫ReleaseMutex函式來釋放該互斥物件:
BOOL ReleaseMutex(HANDLE hMutex);
該函式將物件的遞迴計數器遞減1。
當該物件變為已通知狀態時,系統要檢視是否有任何執行緒正在等待互斥物件。如果有,系統將“按公平原則”選定等待執行緒中的一個,為它賦予互斥物件的所 有權。當然,這意味著執行緒I D被設定為選定的執行緒的ID,並且遞迴計數器被置為1。如果沒有其他執行緒正在等待互斥物件,那麼該互斥物件將保持已通知狀態,這樣,等待互斥物件的下一個 執行緒就立即可以得到互斥物件。
二 API
Mutex function | Description |
---|---|
Creates or opens a named or unnamed mutex object. | |
Creates or opens a named or unnamed mutex object and returns a handle to the object. | |
Opens an existing named mutex object. | |
Releases ownership of the specified mutex object. |
三 例項
來自msdn的例項:線上程函式中有一個迴圈,在每個迴圈的開始都取得Mutex,然後對全域性或靜態操作,相當於在關鍵程式碼段操作,然後在使用完以後釋放它,大家可以執行,查看結果。
#include <windows.h>
#include <stdio.h>
#define THREADCOUNT 64 //less than 64
HANDLE ghMutex;
int g_x =0;
DWORD WINAPI WriteToDatabase(LPVOID);
void main()
{
HANDLE aThread[THREADCOUNT];
DWORD ThreadID;
int i;
// Create a mutex with no initial owner
ghMutex = CreateMutex(
NULL, // default security attributes
FALSE, // initially not owned
NULL); // unnamed mutex
if (ghMutex == NULL)
{
printf("CreateMutex error: %d\n", GetLastError());
return;
}
// Create worker threads
for( i=0; i < THREADCOUNT; i++ )
{
aThread[i] = CreateThread(
NULL, // default security attributes
0, // default stack size
(LPTHREAD_START_ROUTINE) WriteToDatabase,
NULL, // no thread function arguments
0, // default creation flags
&ThreadID); // receive thread identifier
if( aThread[i] == NULL )
{
printf("CreateThread error: %d\n", GetLastError());
return;
}
}
// Wait for all threads to terminate
WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);
// Close thread and mutex handles
for( i=0; i < THREADCOUNT; i++ )
CloseHandle(aThread[i]);
CloseHandle(ghMutex);
printf("g_x is :%d\n",g_x);
}
DWORD WINAPI WriteToDatabase( LPVOID lpParam )
{
DWORD dwCount=0, dwWaitResult;
// Request ownership of mutex.
while( dwCount <100 )
{
dwWaitResult = WaitForSingleObject(
ghMutex, // handle to mutex
INFINITE); // no time-out interval
switch (dwWaitResult)
{
// The thread got ownership of the mutex
case WAIT_OBJECT_0:
__try {
g_x++;
// TODO: Write to the database
printf("Thread %d writing to database\n",
GetCurrentThreadId());
dwCount++;
}
__finally {
// Release ownership of the mutex object
if (! ReleaseMutex(ghMutex))
{
// Deal with error.
}
}
break;
// The thread got ownership of an abandoned mutex
case WAIT_ABANDONED:
return FALSE;
}
}
return TRUE;
}