1、互斥量內核對象
互斥量內核對象用來確保一個線程獨占對一個資源的訪問。互斥量對象包含一個使用計數、線程ID以及遞歸計數。互斥量與關鍵段的行為完全相同。但是互斥量是內核對象,而關鍵段是用戶模式下的同步對象。這意味着互斥量比關鍵段慢。但這同時意味着不同進程中的線程可以訪問同一互斥量,還意味着線程可以在等待對資源的訪問權的同時指定一個最長等待時間。
線程ID用來標識當前占用這個互斥量的是系統中的哪個線程,遞歸計數表示這個線程占用該互斥量的次數。互斥量一般用來對多個線程訪問同一塊內存進行保護。它可以確保正在訪問的內存塊的任何線程會獨占內存塊的訪問權。
互斥量的規則:
- 如果線程ID為0(無效線程ID),那么該互斥量不為任何線程所占用,它處於觸發狀態。
- 如果線程ID為非零值,那么有一個線程已經占用了該信號量,它處於未觸發狀態。
- 於所有其他內核對象不同,操作系統對互斥量進行了特殊處理,允許它們違反一些常規規則。
要使用互斥量,進程必須先調用CreateMutex來創建一個互斥量
1 HANDLE CreateMutex( 2 __in_opt LPSECURITY_ATTRIBUTES lpMutexAttributes, 3 __in BOOL bInitialOwner, 4 __in_opt LPCSTR lpName 5 );
參數bInitialOwner用來控制互斥量的初始狀態。如果傳的是FALSE(通常情況) 那么互斥量對象的線程ID和遞歸計數都被設為0。這意味着互斥量不為任何線程所占用,因此處於觸發狀態。
如果該參數為TRUE,那么對象的線程ID被設定為調用線程的線程ID,遞歸計數將被設定為1。由於線程ID為非零值,因此互斥量最初處於未觸發狀態。
下面就正常情況給出一個例子說明互斥量的常規使用方法:(例子在VS2010下編譯運行)
首先編寫main函數
1 #include <stdio.h> 2 #include <Windows.h> 3 #include <process.h> 4 5 int main(int argc, char* argv[]) 6 { 7 HANDLE hMutex = NULL; 8 hMutex = CreateMutexA(NULL,FALSE,"test1234qwer"); 9 HANDLE hThread1 = CreateThread(NULL, 0, LisDevProc1, (LPVOID)&hMutex, 0, NULL); 10 HANDLE hThread2 = CreateThread(NULL, 0, LisDevProc2, (LPVOID)&hMutex, 0, NULL); 11 12 Sleep(10000); 13 14 if(hThread1) 15 CloseHandle( hThread1 ); 16 17 if(hThread2) 18 CloseHandle( hThread2 ); 19 20 CloseHandle(hMutex); 21 22 system("pause"); 23 return 0; 24 }
該函數創建了1個互斥量及兩個線程,然后等待10s釋放相關資源結束。
1 DWORD WINAPI LisDevProc1(LPVOID para) 2 { 3 HANDLE* phMutex = (HANDLE*)para; 4 5 WaitForSingleObject(*phMutex,INFINITE); 6 7 printf("Enter Thread1\n"); 8 printf("I'm sleeping……\n"); 9 10 Sleep(3000); 11 12 printf("Leave Thread1\n"); 13 14 ReleaseMutex(*phMutex); 15 return 0; 16 }
上述是線程1的處理函數,先等待該互斥量然后sleep3秒然后釋放該互斥量。
線程2處理函數跟線程1相同,如下
1 DWORD WINAPI LisDevProc2(LPVOID para) 2 { 3 HANDLE* phMutex = (HANDLE*)para; 4 5 WaitForSingleObject(*phMutex,INFINITE); 6 7 printf("Enter Thread2\n"); 8 printf("I'm sleeping……\n"); 9 10 Sleep(3000); 11 12 printf("Leave Thread2\n"); 13 14 ReleaseMutex(*phMutex); 15 return 0; 16 }
執行結果:
執行順序是:線程1先通過WaitForSingleObject獲取互斥量的所有權,打印Enter,然后等待3秒,打印Leave,最后釋放互斥量的所有權,然后線程2才獲取到互斥量的所有權。。。可以看到,互斥量確實實現了對共享資源的保護。
上邊還提到了一條特殊的規則,
線程在試圖等待一個未觸發的互斥量對象時,通常線程會進入等待狀態。但是,系統會檢查想要獲得互斥量的線程的線程ID於互斥量內部記錄的線程ID是否相同,如果線程ID一致,那么系統會讓線程保持可調度狀態——即使該互斥量尚未觸發。每次線程成功的等待了一個互斥量,互斥量的遞歸計數會遞增。使遞歸計數大於1的唯一途徑就是利用這一例外,讓線程多次等待同一互斥量。
對上述特例,先把以上代碼中線程1的代碼改為如下:
1 DWORD WINAPI LisDevProc1(LPVOID para) 2 { 3 HANDLE* phMutex = (HANDLE*)para; 4 5 WaitForSingleObject(*phMutex,INFINITE); 6 WaitForSingleObject(*phMutex,INFINITE); 7 printf("Enter Thread1\n"); 8 printf("I'm sleeping……\n"); 9 10 Sleep(3000); 11 12 printf("Leave Thread1\n"); 13 ReleaseMutex(*phMutex); 14 ReleaseMutex(*phMutex); 15 return 0; 16 }
可以看到,線程1的處理函數調用了兩次WaitForSingleObject,結果跟之前結果一樣,印證了該特例確實存在。
以上說來了這么多,才剛剛進入主題,討論一下遺棄問題。
互斥量的這種線程所有權的概念導致出現遺棄問題。
如果占用互斥量的線程在釋放互斥量之前終止(使用ExitThread,TerminateThread,ExitProcess,TerminateProcess)那么對於互斥量和正在等待該互斥量的線程來說會發生什么情況?答案是系統會認為互斥量被遺棄(abandoned),因為占用它的線程已經終止,因此無法釋放它。
因為系統會記錄所有的互斥量和線程內核對象,因此它確切的知道互斥量何時被遺棄。當互斥量被遺棄的時候,系統會自動將互斥量的線程ID設為0,將它的遞歸計數設為0。然后系統檢查有沒有其他線程正在等待該互斥量。如果有,那么系統會公平的選擇一個正在等待的線程,把對象內部的線程Id設為所選擇的那個線程的線程ID,並將遞歸計數設為1,這樣被選擇的線程就變成可調度狀態了。
一旦檢測到某互斥量被檢測到,則WaitForSingleObject返回的不是WAIT_OBJECT_0,而是一個特殊值WAIT_ABANDONED。
返回該值,說明等待的互斥量被某個線程遺棄,同時說明被保護的資源已經被破壞了。這種情況下,寫的程序自己必須決定該怎么做。
看下一下程序代碼:
1 #include <stdio.h> 2 #include <Windows.h> 3 #include <process.h> 4 5 6 DWORD WINAPI LisDevProc1(LPVOID para) 7 { 8 HANDLE* phMutex = (HANDLE*)para; 9 10 WaitForSingleObject(*phMutex,INFINITE); 11 printf("Enter Thread1\n"); 12 printf("I'm sleeping……\n"); 13 14 Sleep(3000); 15 16 printf("Leave Thread1\n"); 17 ReleaseMutex(*phMutex); 18 return 0; 19 } 20 21 DWORD WINAPI LisDevProc2(LPVOID para) 22 { 23 24 HANDLE* phMutex = (HANDLE*)para; 25 int ret; 26 int flag; 27 do{ 28 flag = 0; 29 ret = WaitForSingleObject(*phMutex,INFINITE); 30 switch(ret) 31 { 32 case WAIT_OBJECT_0: 33 printf("normal ....\n"); 34 break; 35 case WAIT_ABANDONED: 36 flag = 1; 37 printf("abandoned ....\n"); 38 break; 39 } 40 }while(flag); 41 printf("Enter Thread2\n"); 42 printf("I'm sleeping……\n"); 43 44 Sleep(3000); 45 46 printf("Leave Thread2\n"); 47 ReleaseMutex(*phMutex); 48 return 0; 49 } 50 51 int main(int argc, char* argv[]) 52 { 53 HANDLE hMutex = NULL; 54 hMutex = CreateMutexA(NULL,FALSE,"test1234qwer"); 55 HANDLE hThread1 = CreateThread(NULL, 0, LisDevProc1, (LPVOID)&hMutex, 0, NULL); 56 HANDLE hThread2 = CreateThread(NULL, 0, LisDevProc2, (LPVOID)&hMutex, 0, NULL); 57 58 Sleep(1500); 59 TerminateThread(hThread1, 0); 60 61 Sleep(10000); 62 63 if(hThread1) 64 CloseHandle( hThread1 ); 65 66 if(hThread2) 67 CloseHandle( hThread2 ); 68 69 CloseHandle(hMutex); 70 71 system("pause"); 72 return 0; 73 }
在main函數中,創建了2個線程后的1.5秒 執行了一句TerminateThread結束了線程1,線程1沒來得及釋放互斥量就掛掉了,(慎重用TerminateThread等函數)看下結果如下:
可以看到在殺死線程1后,線程2的WaitForSingleObject立刻返回WAIT_ABANDONED,然后線程2再次WaitForSingleObject時又立刻返回WAIT_OBJECT_0
最后,一定注意遺棄問題的產生,如果產生,說明受保護的共享數據可能已經被破壞掉了。