學習筆記—多線程同步與互斥

 

多線程編程是一個難點,不僅要考慮各個線程之間的同步和互斥問題,避免死鎖,活鎖和優先級反轉;還要考慮對資源的釋放,做到沒有內存泄露就是最好的實現了。

還記得大學時候,操作系統課程介紹了進程的同步與互斥,用PV原語實現進程間的同步與互斥;當初複習考研的時候也做過很多的練習;前一段時間和最近一段時間也學習過這方面的知識;現在,當前工作中,項目用的就是多線程實現的,裏面不說每種方法都涉及了的,至少涉及了兩三種。

言歸正傳,總結一下多線程同步與互斥實現的一些方法和技術。這些討論是基於windows平臺。

一:單個進程之間多線程的同步技術

1.關鍵段——這個是在用戶模式下的線程同步;

MS 定義了一個CRITICAL_SECTION數據結構,對外沒有公開,在相應的winbase.h或者winnt.h可以找到定義。在內部也使用了Interlocked原子鎖。使用起來很方便,但是,很容易造成死鎖,因爲在等待進入關鍵代碼段時無法設定超時值。

初始化函數:VOID InitializeCriticalSection(PCRITICAL_SECTION pcs)//對pcs指向CRITICAL_SECTION進行初始化

釋放關鍵段資源:VOID DeleteCriticalSection(PCRITICAL_SECTION pcs)

使用:在公共資源出使用 EnterCriticalSection(CRITICAL_SECTION g_cs);

使用完,一定要用LeaveCreaticalSection(CRITICAL_SECTION g_cs)表示對資源不再訪問,其他線程可以對此資源進行訪問。

2.Sim讀/寫鎖——這個是在用戶模式下的線程同步;

對於多個讀取者和多個寫入者,這個是最好的解決辦法;MS定義了一個SRWLOCK結構體使用InitializeSRWLock(PSRWLOCK srwlock)對它進行初始化;Sim讀/寫鎖不需要程序員對他進行釋放,操作系統會自己釋放鎖使用的資源;

對寫入者線程來使用AcquireSRWLockExclusive(PSRWLOCK srwlock)獲取資源使用權,對資源進行更新後調用ReleaseSRWLock(PSRWLOCK srwlock)釋放對共享資源的佔用;

對讀取者線程來說方法有一點小區別,它使用AcquireSRWLockShared(PSRWLOCK srwlock)獲得對共享資源的訪問,多個讀取者(對於多CPU就是實際上的 “同時”)可以同時對共享資源進行讀(他們並不會破壞數據完整性);調用ReleaseSRWLockShared(PSRWLOCK srwlock)解除對資源的訪問權。

3.條件變量——這個是在用戶模式下的線程同步;

對於多個讀取者和多個寫入者,存在一種情況:當讀取者沒有數據可以讀取的時候,應該將鎖釋放;同樣,寫入者寫入的數據已滿就應該釋放鎖並進入睡眠狀態。

這種情況的解決就用到了條件變量:通過SleepConditionVariableCS或者SleepConditionVariableSRW設置條件變量;當調用線程(讀取者檢測到有數據的時候)檢測到條件滿足時,會通過調用WakeConditionVariable或者WakeAllConditionVariable,將阻塞的線程(寫入者阻塞自己)喚醒。

三種線程同步的方法各有利弊,sim讀寫鎖的效率高於關鍵段;所以優先採用sim讀寫鎖,不適用的話可以採用關鍵段。

 

二.多個進程之間同步互斥,採用內核對象實現線程同步;這種方法實現同步會比較慢,CPU花很多時間在用戶模式和內核模式進行切換。

1.事件

用CreateEvent(psa, bool, bool, pszname)創建事件內核對象,第一個參數是安全屬性,第二個是手動還是自動重置事件,第三個表示是將事件初始化爲觸發還是觸發狀態,最後一個對象的名稱。當不需要關注事件對象的句柄時,要用CloseHandle將其關閉。

使用:SetEvent設置成觸發狀態;ResetEvent把事件變成未觸發狀態。

其他進程的線程可以通過調用OpenEvent(DEWORD,bool, pszname) 訪問事件內核對象。

2.可等待計時器

用CreateWaitableTimer(psa,   bool,   pszname)同樣,第二個參數表示是手動還是自動重置事件。再創建的時候總是處於未觸發的狀態,必須調用SetWaitableTimer通知可等待計時器何時觸發,SetWaitableTimer和SetTimer很像,每個一定的時間觸發一次,但是可以設置觸發一次等等。

使用CancelWaitableTimer取消計時器。

3.信號量

和其他的內核對象一樣,包含了一個使用計數,同時還包含了:一個最大資源計數和一個當前資源數;分別是第三個參數和第二個參數;用CreateSemaphore(psa, long, long, pszname),其他進程可以通過OpenSemaphore訪問一個已經存在的信號量內核對象。

使用:等待函數(WaitSingleObject)傳入信號量的句柄,此時,OS會檢測信號量的當前資源計數,如果大於零(有信號狀態),等待函數會遞減資源使用數;使用完後,通過調用ReleaseSemaphore來遞增資源的使用數量。

4.互斥量

互斥量對象包含一個使用計數和線程ID和一個遞歸計數。使用CreateMutex(psa, bool, pszname)或者CreateMutexEx(psa,pszname, dwFlags,   dwDesiredAccess)具體怎麼使用可以查MSDN。

使用等待函數WaitSingleObject來請求對資源的訪問權,使用ReleaseMutex釋放互斥量。

其他進程的線程可以通過OpenMutex訪問一個已經創建了的互斥對象內核對象。由於互斥量有“線程所有權”的概念,操作系統會檢測想要獲得互斥量的線程ID與互斥量線程的線程ID,如果一致就會調度此線程,而不管互斥量是否處於有信號狀態。

 

三.其他的實現線程同步的方法

WaitForInputIdle,MsgWaitForMutilpleObjects(Ex),WaitForDebugEvent,SigalObjectAndWait等等。

 

參考資料:孫鑫《深入詳解MFC》,《Windows核心編程》第五版。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章