前面已經介紹了線程的創建、銷毀過程,如何判斷一個線程是否結束;但是撰寫多線程程序的一個挑戰性問題就是:如何讓一個線程和另外一個線程合作。 在同一時間段會存在多個線程,當這些線程同時存取同一數據時,就會有問題。就像在超市儲物品一樣,來的時候物品箱是空,轉身拿物品准備儲的時候,發現物品箱已被占用了。這時,物品箱就是我所說的同一數據,人指的就是線程了。 線程之間的協調工作由同步機制來完成。同步機制相當於線程之間的紅綠燈系統,負責給某個線程綠燈而給其他線程紅燈進行等待。 注:對同步(synchronous)和異步進行一個說明,所謂的同步:當程序1調用程序2時,程序1停下不動,直到程序2完成回到程序1來,程序1才繼續下去。 Win32 API中SendMessage()就是同步行為,而PostMessage()就是異步行為。 現在,看看第一個同步機制。 一、Critical Sections(臨界區域、關鍵區域) 主要操作有: InitializeCriticalSection EnterCriticalSection LeaveCriticalSection DeleteCriticalSection 通過一個例子說明: [cpp] CRITICAL_SECTION gBoxKey ; DWORD WINAPI ThreadFun(LPVOID n){ //進入關鍵區域(情景:關上物品箱,撥下鑰匙) EnterCreiticalSection (&gBoxKey ); //() //處理一些不可分割的操作。。。。。 //(情景:轉身拿物品,儲物品,去購物。。。。) //離開關鍵區域(情景:打開物品箱,拿出儲存的物品,插上鑰匙) LeaveCreiticalSection (&gBoxKey); //() } void main(){ //初始化全局鎖(情景:生成物品箱的鑰匙 ) InitializeCriticalSection( &gBoxKey ) ; //產生兩個線程(情景:准備兩個人搶一個物品箱 ) HANDLE hMan1 = CreateThread(NULL,0,ThreadFun, ……); HANDLE hMan2 = CreateThread(NULL,0,ThreadFun, ……); CloseHandle(hMan1); CloseHandle(hMan2); //刪除全局鎖(情景:刪除物品箱的鑰匙 ) DeleteCriticalSection( &gBoxKey ) ; } 注意:1、一旦一個線程進入一個critical section,它能夠重復進入該critical section,但每次進入都有對應退出; 2、很難定義最小鎖定時間,如果資源一直被鎖定,你就會阻止其他線程的執行,所以千萬不要在critical section中調用Sleep()或任何Wait函數。 死鎖問題 在使用臨界區域的時候,有可能出現兩個線程互相等待對方的資源從而形成等待的輪回,這種情況稱為“死鎖”。下面總結一下產生死鎖的原因等。 產生死鎖的原因主要是: (1) 因為系統資源不足。 (2) 進程運行推進的順序不合適。 (3) 資源分配不當等。 產生死鎖的四個必要條件: (1)互斥條件:一個資源每次只能被一個進程使用。 (2)請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。 (3)不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。 (4)循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關系。 避免死鎖: 死鎖的預防是通過破壞產生條件來阻止死鎖的產生,但這種方法破壞了系統的並行性和並發性。 死鎖產生的前三個條件是死鎖產生的必要條件,也就是說要產生死鎖必須具備的條件,而不是存在這3個條件就一定產生死鎖,那麼只要在邏輯上回避了第四個條件就可以避免死鎖。 避免死鎖采用的是允許前三個條件存在,但通過合理的資源分配算法來確保永遠不會形成環形等待的封閉進程鏈,從而避免死鎖。該方法支持多個進程的並行執行,為了避免死鎖,系統動態的確定是否分配一個資源給請求的進程。 預防死鎖:具體的做法是破壞產生死鎖的四個必要條件之一。 二、Mutexes(互斥器) 一個時間內只能有一個線程擁有mutex,就像同一時間內只能有一個線程進入同一個Critical Section一樣。無論從共享資源的思路了,還是從程序代碼的編制上,使用Mutexes與使用Critical Sections幾乎都沒有什麼區別; 當然,mutex和critical section還有一些區別: 1、鎖住一個未被擁有的mutex,比鎖住一個未被擁有的critical section,需要花費幾乎100倍的時間; 2、mutex可以跨進程使用,critical section只能在同一進程中使用; 3、等待一個mutex時,可以指定“結束等待”的時間長度,這樣就避免了進入在鎖住過程中出不來的問題(這一點下面接著說明)。 提出問題: 作為 Mutexes機制的提出肯定是有其原因的;我們來看這樣的一個情形,當我拿走鑰匙以後,因為某些因素再也不能回來了,那麼這個箱子便再也不能被使用。也就是說,進入Critical Sections線程若中途當掉了,那麼別了線程是再也進不了Critical Sections(一個資源就這樣浪費了),那些需要進入Critical Sections的線程會停在入口不再執行,線程永遠都結束不了。 解決: 還記得上一章學過的WaitForSingleObject嗎?上一章主要用它等待線程的結束,但這個函數的作用不僅限於此,在這裡,我們再前進一小步,探究WaitForSingleObject這個函數的妙用。 在這裡,我遇到了一個叫mutex核心對象,mutex對激發的定義是:“當沒有任何線程擁有該mutex,而且有一個線程正以Wait…()等待該mutex,該mutex就會短暫地出現激發狀態,使Wait…()得以返回, 那麼在其它的情況,mutex處於未激發狀態”。 好了,我們又進一步的了解了WaitForSingleObject函數,那麼,如何解決Critical Sections所遇到的因難呢?當擁有mutex的線程結束前沒有調用ReleaseMutex(不管該線程是當了,還是忘記調用ReleaseMutex),那麼其它正以WaitForSingleObject()等待此mutex的線程就會收到WAIT_ABANDONED_0。有了這個值,我就解開難題了。 舉例說明: [cpp] HANDLE hBoxKey; DWORD WINAPI ThreadFun(LPVOID n){ //進入關鍵區域(情景:關上物品箱,撥下鑰匙) WaitForSingleObject ( hMutex,INFINITE ); // //處理一些不可分割的操作。。。。。 //(情景:轉身拿物品,儲物品,去購物。。。。) //離開關鍵區域(情景:打開物品箱,拿出儲存的物品,插上鑰匙) ReleaseMutex ( hMutex ); // } void main(){ //初始化全局鎖(情景:生成物品箱的鑰匙 ) hBoxKey = CreateMutex( NULL,FALSE,NULL ); //產生兩個線程(情景:准備兩個人搶一個物品箱 ) HANDLE hMan1 = CreateThread(NULL,0,ThreadFun, ……); HANDLE hMan2 = CreateThread(NULL,0,ThreadFun, ……); CloseHandle(hMan1); CloseHandle(hMan2); //刪除全局鎖(情景:刪除物品箱的鑰匙 ) CloseHandle( hBoxKey ) ; }