秒殺多線程第七篇 經典線程同步 互斥量Mutex

閱讀本篇之前推薦閱讀以下姊妹篇:

秒殺多線程第四篇一個經典的多線程同步問題

秒殺多線程第五篇經典線程同步關鍵段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,所以可以仿照上一篇的實現來寫出代碼

  1. //經典線程同步問題 互斥量Mutex  
  2. #include <stdio.h>  
  3. #include <process.h>  
  4. #include <windows.h>  
  5.   
  6. long g_nNum;  
  7. unsigned int __stdcall Fun(void *pPM);  
  8. const int THREAD_NUM = 10;  
  9. //互斥量與關鍵段  
  10. HANDLE  g_hThreadParameter;  
  11. CRITICAL_SECTION g_csThreadCode;  
  12.   
  13. int main()  
  14. {  
  15.     printf("     經典線程同步 互斥量Mutex\n");  
  16.     printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");  
  17.       
  18.     //初始化互斥量與關鍵段 第二個參數爲TRUE表示互斥量爲創建線程所有  
  19.     g_hThreadParameter = CreateMutex(NULL, FALSE, NULL);  
  20.     InitializeCriticalSection(&g_csThreadCode);  
  21.   
  22.     HANDLE  handle[THREAD_NUM];   
  23.     g_nNum = 0;   
  24.     int i = 0;  
  25.     while (i < THREAD_NUM)   
  26.     {  
  27.         handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);  
  28.         WaitForSingleObject(g_hThreadParameter, INFINITE); //等待互斥量被觸發  
  29.         i++;  
  30.     }  
  31.     WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);  
  32.       
  33.     //銷燬互斥量和關鍵段  
  34.     CloseHandle(g_hThreadParameter);  
  35.     DeleteCriticalSection(&g_csThreadCode);  
  36.     for (i = 0; i < THREAD_NUM; i++)  
  37.         CloseHandle(handle[i]);  
  38.     return 0;  
  39. }  
  40. unsigned int __stdcall Fun(void *pPM)  
  41. {  
  42.     int nThreadNum = *(int *)pPM;  
  43.     ReleaseMutex(g_hThreadParameter);//觸發互斥量  
  44.       
  45.     Sleep(50);//some work should to do  
  46.   
  47.     EnterCriticalSection(&g_csThreadCode);  
  48.     g_nNum++;  
  49.     Sleep(0);//some work should to do  
  50.     printf("線程編號爲%d  全局資源值爲%d\n", nThreadNum, g_nNum);  
  51.     LeaveCriticalSection(&g_csThreadCode);  
  52.     return 0;  
  53. }  

運行結果如下圖:

可以看出,與關鍵段類似,互斥量也是不能解決線程間的同步問題。

       聯想到關鍵段會記錄線程ID即有“線程擁有權”的,而互斥量也記錄線程ID,莫非它也有“線程擁有權”這一說法。

       答案確實如此,互斥量也是有“線程擁有權”概念的。“線程擁有權”在關鍵段中有詳細的說明,這裏就不再贅述了。另外由於互斥量常用於多進程之間的線程互斥,所以它比關鍵段還多一個很有用的特性——“遺棄”情況的處理。比如有一個佔用互斥量的線程在調用ReleaseMutex()觸發互斥量前就意外終止了(相當於該互斥量被“遺棄”了),那麼所有等待這個互斥量的線程是否會由於該互斥量無法被觸發而陷入一個無窮的等待過程中了?這顯然不合理。因爲佔用某個互斥量的線程既然終止了那足以證明它不再使用被該互斥量保護的資源,所以這些資源完全並且應當被其它線程來使用。因此在這種“遺棄”情況下,系統自動把該互斥量內部的線程ID設置爲0,並將它的遞歸計數器復置爲0,表示這個互斥量被觸發了。然後系統將公平地選定一個等待線程來完成調度(被選中的線程的WaitForSingleObject()會返回WAIT_ABANDONED_0)。

 

下面寫二個程序來驗證下:

第一個程序創建互斥量並等待用戶輸入後就觸發互斥量。第二個程序先打開互斥量,成功後就等待並根據等待結果作相應的輸出。詳見代碼:

第一個程序:

  1. #include <stdio.h>  
  2. #include <conio.h>  
  3. #include <windows.h>  
  4. const char MUTEX_NAME[] = "Mutex_MoreWindows";  
  5. int main()  
  6. {  
  7.     HANDLE hMutex = CreateMutex(NULL, TRUE, MUTEX_NAME); //創建互斥量  
  8.     printf("互斥量已經創建,現在按任意鍵觸發互斥量\n");  
  9.     getch();  
  10.     //exit(0);  
  11.     ReleaseMutex(hMutex);  
  12.     printf("互斥量已經觸發\n");  
  13.     CloseHandle(hMutex);  
  14.     return 0;  
  15. }  

第二個程序:

  1. #include <stdio.h>  
  2. #include <windows.h>  
  3. const char MUTEX_NAME[] = "Mutex_MoreWindows";  
  4. int main()  
  5. {  
  6.     HANDLE hMutex = OpenMutex(MUTEX_ALL_ACCESS, TRUE, MUTEX_NAME); //打開互斥量  
  7.     if (hMutex == NULL)  
  8.     {  
  9.         printf("打開互斥量失敗\n");  
  10.         return 0;  
  11.     }  
  12.     printf("等待中....\n");  
  13.     DWORD dwResult = WaitForSingleObject(hMutex, 20 * 1000); //等待互斥量被觸發  
  14.     switch (dwResult)  
  15.     {  
  16.     case WAIT_ABANDONED:  
  17.         printf("擁有互斥量的進程意外終止\n");  
  18.         break;  
  19.   
  20.     case WAIT_OBJECT_0:  
  21.         printf("已經收到信號\n");  
  22.         break;  
  23.   
  24.     case WAIT_TIMEOUT:  
  25.         printf("信號未在規定的時間內送到\n");  
  26.         break;  
  27.     }  
  28.     CloseHandle(hMutex);  
  29.     return 0;  
  30. }  

運用這二個程序時要先啓動程序一再啓動程序二。下面展示部分輸出結果:

結果一.二個進程順利執行完畢:

結果二.將程序一中//exit(0);前面的註釋符號去掉,這樣程序一在觸發互斥量之前就會因爲執行exit(0);語句而且退出,程序二會收到WAIT_ABANDONED消息並輸出“擁有互斥量的進程意外終止”:

有這個對“遺棄”問題的處理,在多進程中的線程同步也可以放心的使用互斥量。

 

最後總結下互斥量Mutex

1.互斥量是內核對象,它與關鍵段都有“線程所有權”所以不能用於線程的同步。

2.互斥量能夠用於多個進程之間線程互斥問題,並且能完美的解決某進程意外終止所造成的“遺棄”問題。

 

下一篇《秒殺多線程第八篇 經典線程同步 信號量Semaphore》將介紹使用信號量Semaphore來解決這個經典線程同步問題。

 

 

轉載請標明出處,原文地址:http://blog.csdn.net/morewindows/article/details/7470936

如果覺得本文對您有幫助,請點擊支持一下,您的支持是我寫作最大的動力,謝謝。

發佈了0 篇原創文章 · 獲贊 0 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章