17.[個人]C++線程入門到進階(17)----關鍵段,事件,互斥量,信號量的“遺留”問題

在《秒殺多線程第九篇 經典線程同步總結 關鍵段 事件 互斥量 信號量》中對經典多線程同步互斥問題進行了回顧和總結,這篇文章對Windows系統下常用的線程同步互斥機制——關鍵段事件互斥量信號量進行了總結。有網友問到互斥量能處理“遺棄”問題,事件和信號量是否也能處理“遺棄”問題。因此本文將對事件和信號量作個試驗,看看事件和信號量能否處理“遺棄”問題。

 

一.什麼是“遺棄”問題

在《秒殺多線程第七篇 經典線程同步 互斥量Mutex》講到了互斥量能處理“遺棄”問題,下面引用原文:

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

可見“遺棄”問題就是——佔有某種資源的進程意外終止後,其它等待該資源的進程能否感知。

 

二.關鍵段的“遺棄”問題

關鍵段在這個問題上很簡單——由於關鍵段不能跨進程使用,所以關鍵段不需要處理“遺棄”問題。

 

三.事件,互斥量,信號量的“遺棄”問題

事件,互斥量,信號量都是內核對象,可以跨進程使用。一個進程在創建一個命名的事件後其它進程可以調用OpenEvent()並傳入事件的名稱來獲得這個事件的句柄。因此事件,互斥量和信號量都會遇到“遺棄”問題。我們已經知道互斥量能夠處理“遺棄”問題,接下來就來看看事件和信號量是否能夠處理“遺棄”問題。類似於秒殺多線程第七篇 經典線程同步互斥量Mutex對互斥量所做的試驗,下面也對事件和信號量作同樣的試驗:

1. 創建二個進程。

2. 進程一創建一個初始爲未觸發的事件,然後等待按鍵,按下y則觸發事件後結束進程,否則直接退出表示進程一已意外終止。

3. 進程二先獲得事件的句柄,然後調用WaitForSingleObject()等待這個事件10秒,在這10秒內如果事件已經觸發則輸出“已收到信號”,否則輸出“未在規定的時間內收到信號”。如果在等待的過程中進程一意外終止,則輸出擁有事件的進程意外終止。信號量的試驗方法類似。

爲了加強對比效果,將互斥量的試驗結果先展示出來(代碼請參見《秒殺多線程第七篇經典線程同步 互斥量Mutex》)

可以看出在第一個進程在沒有觸發互斥量就直接退出的情況下,等待這個互斥量的第二個進程是能夠感知進程一所發生的意外終止的。

接下來就先完成事件的“遺棄”問題試驗代碼。

進程一:

[cpp] view plain copy
  1. #include <stdio.h>  
  2. #include <conio.h>  
  3. #include <windows.h>  
  4. const TCHAR STR_EVENT_NAME[] = TEXT("Event_MoreWindows");  
  5. int main()  
  6. {  
  7.     printf("     經典線程同步 事件的遺棄處理  進程一\n");    
  8.     printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");    
  9.     HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, STR_EVENT_NAME);//自動置位 當前未觸發  
  10.     printf("事件已經創建,現在按y觸發事件,按其它鍵終止進程\n");  
  11.     char ch;  
  12.     scanf("%c", &ch);  
  13.     if (ch != 'y')  
  14.         exit(0); //表示進程意外終止  
  15.     SetEvent(hEvent);  
  16.     printf("事件已經觸發\n");  
  17.     CloseHandle(hEvent);  
  18.     return 0;  
  19. }  

進程二:

[cpp] view plain copy
  1. #include <stdio.h>  
  2. #include <windows.h>  
  3. const TCHAR STR_EVENT_NAME[] = TEXT("Event_MoreWindows");  
  4. int main()  
  5. {  
  6.     printf("     經典線程同步 事件的遺棄處理  進程二\n");    
  7.     printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");    
  8.   
  9.     HANDLE hEvent = OpenEvent(EVENT_ALL_ACCESS, TRUE, STR_EVENT_NAME); //打開事件  
  10.     if (hEvent == NULL)  
  11.     {  
  12.         printf("打開事件失敗\n");  
  13.         return 0;  
  14.     }  
  15.     printf(" 等待中....\n");  
  16.     DWORD dwResult = WaitForSingleObject(hEvent, 10 * 1000); //等待事件被觸發  
  17.     switch (dwResult)  
  18.     {  
  19.     case WAIT_ABANDONED:  
  20.         printf("擁有事件的進程意外終止\n");  
  21.         break;  
  22.   
  23.     case WAIT_OBJECT_0:  
  24.         printf("已經收到信號\n");  
  25.         break;  
  26.   
  27.     case WAIT_TIMEOUT:  
  28.         printf("未在規定的時間內收到信號\n");  
  29.         break;  
  30.     }  
  31.     CloseHandle(hEvent);  
  32.     return 0;  
  33. }  

事件Event試驗結果1-進程一觸發事件後正常結束:

事件Event試驗結果2-進程一意外終止:

可以看出進程二沒能感知進程一意外終止,說明事件不能處理“遺棄”問題。

 

下面再來試下信號量。

信號量的“遺棄”問題試驗代碼:

進程一:

[cpp] view plain copy
  1. #include <stdio.h>  
  2. #include <conio.h>  
  3. #include <windows.h>  
  4. const TCHAR STR_SEMAPHORE_NAME[] = TEXT("Semaphore_MoreWindows");  
  5. int main()  
  6. {  
  7.     printf("     經典線程同步 信號量的遺棄處理  進程一\n");    
  8.     printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");    
  9.   
  10.     HANDLE hSemaphore = CreateSemaphore(NULL, 0, 1, STR_SEMAPHORE_NAME);//當前0個資源,最大允許1個同時訪問  
  11.     printf("信號量已經創建,現在按y觸發信號量,按其它鍵終止進程\n");  
  12.     char ch;  
  13.     scanf("%c", &ch);  
  14.     if (ch != 'y')  
  15.         exit(0); //表示進程意外終止  
  16.     ReleaseSemaphore(hSemaphore, 1, NULL);  
  17.     printf("信號量已經觸發\n");  
  18.     CloseHandle(hSemaphore);  
  19.     return 0;  
  20. }  

進程二:

[cpp] view plain copy
  1. #include <stdio.h>  
  2. #include <windows.h>  
  3. const TCHAR STR_SEMAPHORE_NAME[] = TEXT("Semaphore_MoreWindows");  
  4. int main()  
  5. {  
  6.     printf("     經典線程同步 信號量的遺棄處理  進程二\n");    
  7.     printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");    
  8.   
  9.     HANDLE hSemaphore = OpenSemaphore (SEMAPHORE_ALL_ACCESS, TRUE, STR_SEMAPHORE_NAME); //打開信號量  
  10.     if (hSemaphore == NULL)  
  11.     {  
  12.         printf("打開信號量失敗\n");  
  13.         return 0;  
  14.     }  
  15.     printf(" 等待中....\n");  
  16.     DWORD dwResult = WaitForSingleObject(hSemaphore, 10 * 1000); //等待信號量被觸發  
  17.     switch (dwResult)  
  18.     {  
  19.     case WAIT_ABANDONED:  
  20.         printf("擁有信號量的進程意外終止\n");  
  21.         break;  
  22.   
  23.     case WAIT_OBJECT_0:  
  24.         printf("已經收到信號\n");  
  25.         break;  
  26.   
  27.     case WAIT_TIMEOUT:  
  28.         printf("未在規定的時間內收到信號\n");  
  29.         break;  
  30.     }  
  31.     CloseHandle(hSemaphore);  
  32.     return 0;  
  33. }  

信號量Semaphore試驗結果1-進程一觸發信號量後正常結束

信號量Semaphore試驗結果2-進程一意外終止

可以看出進程二沒能感知進程一意外終止,說明信號量與事件一樣都不能處理“遺棄”問題。

 

四.“遺棄”問題總結

由本文所做的試驗可知,互斥量能夠處理“遺棄”情況,事件與信號量都無法解決這一情況。

再思考下互斥量能處理“遺棄”問題的原因,其實正是因爲它有“線程所有權”概念。在系統中一旦有線程結束後,系統會判斷是否有互斥量被這個線程佔有,如果有,系統會將這互斥量對象內部的線程ID號將設置爲NULL,遞歸計數設置爲0,這表示該互斥量已經不爲任何線程佔用,處於觸發狀態。其它等待這個互斥量的線程就能順利執行下去了。至於線程如何獲取互斥量的“線程所有權”,MSDN上介紹爲——A thread obtainsownership of a mutex either by creating it with the bInitialOwnerparameter set to TRUE or by specifying its handle in a call toone of the wait functions.

文章到這就結束了,有問題歡迎留言或發送郵件:[email protected]

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