windows核心編程-用內核對象進行線程同步

前言:

    在用戶模式下進行線程同步的最大好處就是速度非常快。與用戶模式下的同步機制相比,內核對象的用途要廣泛的多,內核對象唯一的缺點就是它們的性能。調用本章介紹的任何一個新函數時,調用線程必須從用戶模式切換到內核模式。對於線程同步來說,這些內核對象中的每一種要麼處於觸發狀態,要麼處於未觸發狀態。比如進程創建時,進程內核對象爲未觸發狀態;進程結束時,進程內核對象爲觸發狀態,當進程內核對象被觸發後,它將永遠保持這種狀態,再也不會回到未觸發狀態。線程內核對象同進程內核對象。

    下面的內核對象既可以處於觸發狀態,也可以處於未觸發狀態。如進程、線程、作業、文件以及控制檯的標準輸入流/輸出流/錯誤流、事件、可等待的計時器、信號量、互斥量。線程可以自己切換到等待狀態,直到另一個對象被觸發爲止。用來決定每個對象處於觸發狀態還是未觸發狀態的規則與對象的類型有關。我們可以把每個內核對象想象爲都包含一面旗幟,當對象被觸發的時候,旗幟升起;當對象爲被觸發的時候,旗幟落下。當線程正在等待的對象處於未觸發狀態(旗幟落下)的時候,它們是不可調度的。但是一旦對象被觸發(旗幟升起),那麼線程就會看到這面旗幟,從而變成可調度狀態,然後很快就會繼續執行。

 

9.1 等待函數

    等待函數使一個線程自願進入等待狀態,直到指定的內核對象被觸發爲止。注意,如果線程在調用一個等待函數的時候,相應的內核對象已經處於觸發狀態,那麼線程是不會進入等待狀態的。等待函數不會浪費寶貴的CPU時間,等待函數中最常用的是WaitForSingleObject,該函數的返回值表示爲什麼調用線程又能繼續執行了。WaitForMultipleObjects允許調用線程同時檢查多個內核對象的觸發狀態。

   

9.2 等待成功所引起的副作用

    對一些內核對象來說,成功調用WaitForSingleObject或WaitForMultipleObjects事實上會改變對象的狀態。如果對象的狀態發生了改變,則稱之爲等待成功所引起的副作用。如現在假設線程正在等待一個自動重置事件對象,當事件對象被觸發的時候,上述函數會檢測到這一情況,這時它可以直接返回WAIT_OBJECT_0給調用線程。但是就在函數返回之前,上述函數會使事件變爲非觸發狀態——這就是等待成功所引起的副作用。

    WaitForMultipleObjects之所以這麼有用的原因,是因爲它能夠以原子方式執行所有的操作。當線程調用WaitForMultipleObjects的時候,函數會測試所有對象的觸發狀態,並引發相應的副作用,所以這些都是作爲一個操作來完成的。當該函數檢查內核對象的狀態時,任何其他線程都不能在背後修改對象的狀態。

    如果多個線程等待同一個內核對象,那麼當對象被觸發的時候,系統如何決定應該喚醒哪個線程?可能會喚醒任何一個,不論優先級高低、等待時間長短,對每個線程來說都有機會被喚醒,且機會是一樣的,微軟的解釋是“算法是公平的”,實際上微軟所使用的算法只不過是總所周知的“先入先出”機制。

 

9.3 事件內核對象(Event)

    

態。第二個線程知道第一個線程已經完成了它的工作。

自動重置事件可以想象爲自動門,觸發後(打開),只允許一個正在等待該事件的線程會變成可調度狀態(只允許一個人進入,進入後就門就會被關閉);

手動重置事件可以想象爲手動門,觸發後(打開),正在等待該事件的所有線程都將變成可調度狀態(允許所有人進入,直到門關閉)

    CreateEvent總是被授予全部權限。但CreateEventEx更有用的地方在於它允許我們減少權限的方式來打開一個已經存在的時間,而CreateEvent總是要求全部權限。

    一旦創建了事件,我們就可以直接控制它的狀態。當調用SetEvent的時候,我們把事件變成觸發狀態;當調用ResetEvent的時候,我們把事件變成未觸發狀態。

    微軟爲自動重置事件定義了一個等待成功所引起的副作用:當線程成功等待自動重置事件對象的時候,對象會自動地重置爲未觸發狀態。對自動重置對象來說,通常不需要調用ResetEvent,這是因爲系統會自動將事件重置。

最後總結下事件Event
1.事件是內核對象,事件分爲手動置位事件和自動置位事件。事件Event內部它包含一個使用計數(所有內核對象都有),一個布爾值表示是手動置位事件還是自動置位事件 ,另一個布爾值用來表示事件有無觸發。
2.事件可以由SetEvent()來觸發,由ResetEvent()來設成未觸發。還可以由PulseEvent()來發出一個事件脈衝。
3.事件可以解決線程間同步問題,因此也能解決互斥問題。

   

9.4 可等待的計時器內核對象(WaitableTimer)

    可等待的計時器是這樣一種內核對象,它們會在某個指定的時間觸發,或每隔一段時間觸發一次。它們通常用來在某個時間執行一些操作。和事件內核對象一樣有手動重置和自動重置,方式也是一樣的。

    可以通過CreateWaitableTimer來創建一個可等待的計時器內核對象,在創建的時候可等待的計時器總是處於未觸發狀態。我們必須調用SetWaitableTimer來觸發它的狀態。每次調用SetWaitableTimer都會在設置新的觸發時間之前將原來的觸發時間取消。CancelWaitableTimer會把句柄標識的計時器取消,這樣計時器就永遠不會再觸發了,除非以後再調用SetWaitableTimer來對它進行重置。

9.4.1 讓可等待的計時器添加APC(異步過程調用)調用

    當計時器觸發的時候,微軟還允許計時器把一個異步過程調用(APC)放到SetWaitableTimer的調用線程的隊列中。

 

9.5 信號量內核對象(Semaphore)

    信號量內核對象用來對資源進行計數。與其他所有內核對象相同,它們也包含一個使用計數,另外還包括兩個32位值:一個最大資源計數和一個當前資源計數。最大資源計數表示信號量可以控制的最大資源數量,當前資源計數表示信號量當前可用資源的數量。

    信號量的規則如下:

    ①如果當前資源計數大於0,那麼信號量處於觸發狀態

    ②如果當前資源計數等於0,那麼信號量處於未觸發狀態

    ③系統絕對不會讓當前資源計數變爲負數

    ④當前資源計數絕對不會大於最大資源計數

 

    可以通過CreateSemaphore來創建一個信號量內核對象,線程可以通過ReleaseSemaphore來遞增信號量的當前資源計數。

注意:當前資源數量大於0,表示信號量處於觸發,等於0表示資源已經耗盡故信號量處於末觸發。在對信號量調用等待函數時,等待函數會檢查信號量的當前資源計數,如果大於0(即信號量處於觸發狀態),減1後返回讓調用線程繼續執行。一個線程可以多次調用等待函數來減小信號量。 

9.6 互斥量內核對象(Mutex)

    互斥量內核對象用來確保一個線程獨佔對一個資源的訪問。互斥量包含了一個使用計數、線程ID和一個遞歸計數。互斥量和關鍵段的行爲完全相同。但是互斥量是內核對象,而關鍵段是用戶模式下的同步對象。線程ID用來標識當前佔用這個互斥量的是系統中的哪個線程,遞歸計數表示這個線程佔用該互斥量的次數。互斥量有很多用途,它們是使用最爲頻繁的內核對象之一。它們一般用來對多個線程訪問的同一塊內存進行保護。互斥量可以確保正在訪問內存塊的任何線程獨佔對內存塊的訪問權,這樣就維護了數據的完整性。互斥量只能用於互斥,不能用於同步。

9.6.1 遺棄問題(線程所有權)

    互斥量與所有其他內核對象不同,這是因爲它們具有“線程所有權”的概念。互斥量會記住自己是哪個線程等待成功的,這使它即使在未觸發的狀態下,也能爲線程所獲得。這個例外不僅適用於試圖獲得互斥量的線程,而且適用於試圖釋放互斥量的線程。當線程調用ReleaseMutex的時候,函數會檢查調用線程ID與互斥量內部保存的線程ID是否一致。如果線程ID一致,遞歸計數會遞減。如果線程ID不一致,那麼ReleaseMutex將不執行任何操作並返回false給調用者。

    

最後總結下互斥量Mutex:
1.互斥量是內核對象,它與關鍵段都有“線程所有權”所以不能用於線程的同步。
2.互斥量能夠用於多個進程之間線程互斥問題,並且能完美的解決某進程意外終止所造成的“遺棄”問題。

9.8 其他的線程同步函數

9.8.1 異步設備I/O

    當系統執行異步I/O操作的時候,設備對象處於未觸發狀態。一旦操作完成,系統會將對象變成觸發狀態。這樣線程就知道操作已經完成了。這時,線程就可以繼續執行。

9.8.2 WaitForInputIdle函數

    線程可以調用這個函數來將自己掛起。比較實用的場景是:應用程序想要發送信息給一個窗口,但卻不知道窗口什麼時候能夠創建完畢並就緒。該函數可以解決上述問題。

9.8.3 MsgWaitForMultipleObjects(Ex)函數

    線程也可以調用該類函數,這使得線程等待需要自己處理的消息。這類函數與WaitForMultipleObjects類似。不同之處在於,不僅內核對象被觸發的時候調用線程會變成可調度狀態,而且當窗口消息需要被派送到一個由調用線程創建的窗口時,它們也會變成可調度狀態。創建窗口的線程和執行與用戶界面相關的任務的線程不應該使用WaitForMultipleObjects,而應該使用MsgWaitForMultipleObjects(Ex)。這是因爲前者會妨礙線程對用戶在用戶界面上的操作進行響應。

9.8.4 WaitForDebugEvent函數

9.8.5 SignalObjectAndWait函數

    該函數會通過一個原子操作來觸發一個內核對象並等待另一個內核對象。觸發的內核對象只能爲以下幾種:互斥量、信號量或事件。等待的內核對象可以是內核對象的任意一種。這個函數受歡迎的幾個原因:首先,因爲我們經常需要觸發一個內核對象並等待另一個對象,讓一個函數完成兩個操作可以節省處理時間;其次,如果沒有SignalObjectAndWait函數,那麼一個線程就無法知道另一線程何時處於等待狀態;

9.8.6 使用等待鏈遍歷API來檢測死鎖

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