閱讀本篇之前推薦閱讀以下姊妹篇:
《秒殺多線程第四篇一個經典的多線程同步問題》
《秒殺多線程第五篇經典線程同步關鍵段CS》
《秒殺多線程第六篇經典線程同步事件Event》
前面介紹了關鍵段CS、事件Event在經典線程同步問題中的使用。本篇介紹用互斥量Mutex來解決這個問題。
互斥量也是一個核心對象,它用來確保一個線程獨佔一個資源的訪問。互斥量與關鍵段的行為非常相似,並且互斥量可以用於不同進程中的線程互斥訪問資源。使用互斥量Mutex主要將用到四個函數。下面是這些函數的原型和使用說明。
第一個 CreateMutex
函數功能:建立互斥量(注意與事件Event的建立函數對比)
函數原型:
HANDLECreateMutex(
LPSECURITY_ATTRIBUTESlpMutexAttributes,
BOOLbInitialOwner,
LPCTSTRlpName
);
函數說明:
第一個參數表示安全控制,一般直接傳入NULL。
第二個參數用來確定互斥量的初始擁有者。如果傳入TRUE表示互斥量對象內部會記錄建立它的線程的線程ID號並將遞迴計數設定為1,由於該線程ID非零,所以互斥量處於未觸發狀態。如果傳入FALSE,那麼互斥量對象內部的線程ID號將設定為NULL,遞迴計數設定為0,這意味互斥量不為任何線程佔用,處於觸發狀態。
第三個參數用來設定互斥量的名稱,在多個進程中的線程就是通過名稱來確保它們訪問的是同一個互斥量。
函數訪問值:
成功返回一個表示互斥量的控制代碼,失敗返回NULL。
第二個開啟互斥量
函數原型:
HANDLEOpenMutex(
DWORDdwDesiredAccess,
BOOLbInheritHandle,
LPCTSTRlpName //名稱
);
函數說明:
第一個參數表示存取權限,對互斥量一般傳入MUTEX_ALL_ACCESS。詳細解釋可以查看MSDN文檔。
第二個參數表示互斥量控制代碼繼承性,一般傳入TRUE即可。
第三個參數表示名稱。某一個進程中的線程建立互斥量後,其它進程中的線程就可以通過這個函數來找到這個互斥量。
函數訪問值:
成功返回一個表示互斥量的控制代碼,失敗返回NULL。
第三個觸發互斥量
函數原型:
BOOLReleaseMutex (HANDLEhMutex)
函數說明:
訪問互斥資源前應該要調用等待函數,結束訪問時就要調用ReleaseMutex()來表示自己已經結束訪問,其它線程可以開始訪問了。
最後一個清理互斥量
由於互斥量是核心對象,因此使用CloseHandle()就可以(這一點所有核心對象都一樣)。
接下來我們就在經典多線程問題用互斥量來保證主線程與子線程之間的同步,由於互斥量的使用函數類似於事件Event,所以可以仿照上一篇的實現來寫出代碼:
//經典線程同步問題 互斥量Mutex#include <stdio.h>#include <process.h>#include <windows.h>long g_nNum;unsigned int __stdcall Fun(void *pPM);const int THREAD_NUM = 10;//互斥量與關鍵段HANDLE g_hThreadParameter;CRITICAL_SECTION g_csThreadCode;int main(){printf(" 經典線程同步 互斥量Mutex\n");printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");//初始化互斥量與關鍵段 第二個參數為TRUE表示互斥量為建立線程所有g_hThreadParameter = CreateMutex(NULL, FALSE, NULL);InitializeCriticalSection(&g_csThreadCode);HANDLE handle[THREAD_NUM];g_nNum = 0;int i = 0;while (i < THREAD_NUM) {handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);WaitForSingleObject(g_hThreadParameter, INFINITE); //等待互斥量被觸發i++;}WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);//銷毀互斥量和關鍵段CloseHandle(g_hThreadParameter);DeleteCriticalSection(&g_csThreadCode);for (i = 0; i < THREAD_NUM; i++)CloseHandle(handle[i]);return 0;}unsigned int __stdcall Fun(void *pPM){int nThreadNum = *(int *)pPM;ReleaseMutex(g_hThreadParameter);//觸發互斥量Sleep(50);//some work should to doEnterCriticalSection(&g_csThreadCode);g_nNum++;Sleep(0);//some work should to doprintf("線程編號為%d 全域資源值為%d\n", nThreadNum, g_nNum);LeaveCriticalSection(&g_csThreadCode);return 0;}
運行結果如:
可以看出,與關鍵段類似,互斥量也是不能解決線程間的同步問題。
聯想到關鍵段會記錄線程ID即有“線程擁有權”的,而互斥量也記錄線程ID,莫非它也有“線程擁有權”這一說法。
答案確實如此,互斥量也是有“線程擁有權”概念的。“線程擁有權”在關鍵段中有詳細的說明,這裡就不再贅述了。另外由於互斥量常用於多進程之間的線程互斥,所以它比關鍵段還多一個很有用的特性——“遺棄”情況的處理。比如有一個佔用互斥量的線程在調用ReleaseMutex()觸發互斥量前就意外終止了(相當於該互斥量被“遺棄”了),那麼所有等待這個互斥量的線程是否會由於該互斥量無法被觸發而陷入一個無窮的等待過程中了?這顯然不合理。因為佔用某個互斥量的線程既然終止了那足以證明它不再使用被該互斥量保護的資源,所以這些資源完全並且應當被其它線程來使用。因此在這種“遺棄”情況下,系統自動把該互斥量內部的線程ID設定為0,並將它的遞迴計數器複置為0,表示這個互斥量被觸發了。然後系統將“公平地”選定一個等待線程來完成調度(被選中的線程的WaitForSingleObject()會返回WAIT_ABANDONED_0)。
下面寫二個程式來驗證下:
第一個程式建立互斥量並等待使用者輸入後就觸發互斥量。第二個程式先開啟互斥量,成功後就等待並根據等待結果作相應的輸出。詳見代碼:
第一個程式:
#include <stdio.h>#include <conio.h>#include <windows.h>const char MUTEX_NAME[] = "Mutex_MoreWindows";int main(){HANDLE hMutex = CreateMutex(NULL, TRUE, MUTEX_NAME); //建立互斥量printf("互斥量已經建立,現在按任意鍵觸發互斥量\n");getch();//exit(0);ReleaseMutex(hMutex);printf("互斥量已經觸發\n");CloseHandle(hMutex);return 0;}
第二個程式:
#include <stdio.h>#include <windows.h>const char MUTEX_NAME[] = "Mutex_MoreWindows";int main(){HANDLE hMutex = OpenMutex(MUTEX_ALL_ACCESS, TRUE, MUTEX_NAME); //開啟互斥量if (hMutex == NULL){printf("開啟互斥量失敗\n");return 0;}printf("等待中....\n");DWORD dwResult = WaitForSingleObject(hMutex, 20 * 1000); //等待互斥量被觸發switch (dwResult){case WAIT_ABANDONED:printf("擁有互斥量的進程意外終止\n");break;case WAIT_OBJECT_0:printf("已經收到訊號\n");break;case WAIT_TIMEOUT:printf("訊號未在規定的時間內送到\n");break;}CloseHandle(hMutex);return 0;}
運用這二個程式時要先啟動程式一再啟動程式二。下面展示部分輸出結果:
結果一.二個進程順利執行完畢:
結果二.將程式一中//exit(0);前面的注釋符號去掉,這樣程式一在觸發互斥量之前就會因為執行exit(0);語句而且退出,程式二會收到WAIT_ABANDONED訊息並輸出“擁有互斥量的進程意外終止”:
有這個對“遺棄”問題的處理,在多進程中的線程同步也可以放心的使用互斥量。
最後總結下互斥量Mutex:
1.互斥量是核心對象,它與關鍵段都有“線程所有權”所以不能用於線程的同步。
2.互斥量能夠用於多個進程之間線程互斥問題,並且能完美的解決某進程意外終止所造成的“遺棄”問題。
下一篇《秒殺多線程第八篇 經典線程同步 訊號量Semaphore》將介紹使用訊號量Semaphore來解決這個經典線程同步問題。
轉載請標明出處,原文地址:http://blog.csdn.net/morewindows/article/details/7470936
如果覺得本文對您有協助,請點擊‘頂’支援一下,您的支援是我寫作最大的動力,謝謝。