Windows核心編程:用內核對象進行線程同步

 

作者:shenzi

鏈接:http://blog.csdn.net/shenzi

Windows核心編程:用內核對象進行線程同步
1.概述
    用戶模式下的同步讓線程保持在用戶模式下,在用戶模式下的進行線程同步的最大好處就是速度非常快。然而它們的確存在一些侷限性,使用內核對象進行線程同步用途要廣泛得多。然而使用內核對象進行線程同步,必須進行用戶模式與內核模式的切換,所以性能上會大打折扣。

    我 們已經討論了好幾種內核對象,包括進程、線程以及作業。幾乎所有這些內核對象都可以用來進行同步。對線程同步來說,這些內核對象中的每一種要麼處於觸發狀 態,要麼處於未觸發狀態。Microsooft爲每種對象創建了一些規則,規定如何在這兩種狀態之間進行轉換。用內核對象進行線程同步就是要用這些內核對 象的規則爲線程同步服務。
2.等待函數
    等待函數 使一個線程自願進入等待狀態,直到指定的內核對象被觸發爲止。注意,如果線程在調用一個等待函數的時候,相應的內核對象已經處於觸發狀態,那麼線程時不會進入等待狀態的。
    DWORD WaitForSingleObject(
        HANDLE hObject, //標識要等待的內核對象,這個內核對象可以處於觸發或未觸發狀態;
        DWORD dwMilliseconds); //指定線程最多願意花多長的時間來等待對象被觸發;

    通常,我們會給dwMilliseconds傳入INFINITE,但也可以傳任何其它的值(以微妙微單位)。傳INFINITE可能會有點危險。如果對象永遠不被觸發,那麼調用線程將永遠不會被喚醒——它會一直阻塞在那裏,但幸運的是,它並沒有浪費寶貴的CPU時間。
    DWORD dw = WaitForSingleObject(hProcess, 5000);
    switch (dw) {
        case WAIT_OBJECT_0:
         // 等待對象被觸發,即等待進程已終止;
        break;
        case WAIT_TIMEOUT:
         // 等待超時,即等待的進程在5000微妙內未終止;
        break;
        case WAIT_FAILED:
        // 無效的參數,如無效的進程句柄
        break;
    }

    前述代碼告訴系統,除非指定的進程已經終止或者等待時間已滿5000微妙,否則不應該對調用線程進行調度。如果進程已經終止,那麼這個調用會在5000微妙內返回,如果進程尚未終止,那麼這個調用大約會在5000微妙左右返回。注意,如果給dwMilliseconds傳0,WaitForSingleObject總會立即返回,即使它要等待的條件還沒有滿足。
    還有一個函數允許調用線程同時檢查多個內核對象的觸發狀態:
    DWORD WaitForMultipleObjects(
        DWORD dwCount,//表示希望檢查的內核對象的數量;最大值MAXIMUM_WAIT_OBJECTS=64
        CONST HANDLE* phObjects,//指向內核對象句柄數組
        BOOL bWaitAll,//TRUE,等待所有內核對象觸發;FALSE,等待其中任一對象觸發;
        DWORD dwMilliseconds);
//指定線程最多願意花多長的時間來等待對象被觸發;


    HANDLE3];
    h[0] = hProcess1;
    h[1] = hProcess2;
    h[2] = hProcess3;
    DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000);
    switch (dw) {
        case WAIT_FAILED:
        // Bad call to function (invalid handle?)
        break;
        case WAIT_TIMEOUT:
        // None of the objects became signaled within 5000 milliseconds.
        break;
        //指定具體哪個進程觸發引起返回的
        case WAIT_OBJECT_0 + 0:
        // The process identified by h[0] (hProcess1) terminated.
        break;
        case WAIT_OBJECT_0 + 1:
        // The process identified by h[1] (hProcess2) terminated.
        break;
        case WAIT_OBJECT_0 + 2:
        // The process identified by h[2] (hProcess3) terminated.
        break;
    }

3.等待成功所引起的副作用
    我們在調用WaitForSingleObject或WaitForMultipleObjects有的時候可能 會改變對象的狀態。如果對象的狀態發生了改變,我們稱之爲等待成功所引起的副作用。 舉 個例子,現在假設線程正在等待一個自動重置事件對象(稍後會講到)。當事件對象被觸發的時候,函數會檢測到這一情況,這時它可以直接返回 WAIT_OBJECT_0給調用線程。但是,就在函數返回之前,它會使事件變爲非觸發狀態——這就是等待成功所引起的副作用。然而進程和線程對象就完全 沒有副作用,也就是說,等待這些對象絕對不會改變對象的狀態。
4.事件內核對象
    事件的觸發表示一個操作已經完成。有兩種不同類型的事件對象:手動重置事件自動重置事件。 當一個手動重置事件被觸發的時候,正在等待該事件的所有線程都將變爲可調度狀態。而當自動重置事件被觸發的時候,只有一個正在等待該事件的線程會變成可調度狀態。
    事件最通常的用途是,讓一個線程執行初始化工作,然後再觸發另一個線程,讓它執行剩餘的工作。以開始我麼麼你 將事件初始化爲爲觸發狀態,然後當線程完成初始化工作的時候,觸發事件。此時,另一個線程一直在等待該事件,它發現事件被觸發,於是鞭策很能夠可調度狀 態。第二個線程知道第一個線程已經完成了它的工作。
    下面是CreateEvent函數,用來創建一個事件內核對象:
    HANDLE CreateEvent(
        PSECURITY_ATTRIBUTES psa,
        BOOL bManualReset,//TRUE,創建手動重置事件;FALSE,創建自動重置事件。
        BOOL bInitialState,//TRUE,初始化爲觸發狀態;FALSE,初始化爲未觸發狀態。
        PCTSTR pszName);

    一旦創建了事件,我們就可以直接控制它的狀態。當調用SetEvent的時候,我們把時間變成觸發狀態:
    BOOL SetEvent(HANDLE hEvent);

    當調用ResetEvent的時候,我們把時間變成未觸發狀態:
    BOOL ResetEvent(HANDLE hEvent);

    Microsoft爲自動重置事件 定義了一個等待成功所引起 的副作用:當線程成功等到自動重置事件對象的時候,對象會自動地重置爲未觸發狀態。這也是自動重置事件名字的由來。對自動重置事件來說,通常不需要調用 ResetEvent,這是因爲系統會自動將事件重置。相反,Microsoft並沒爲手動重置事件對象定義一個等待成功所引起的副作用。
5.可等待的計時器內核對象
    可等待的計時器是這樣一種內核對象,它們會在某個指定的事件觸發,或沒隔一段時間觸發一次。它們通常用來在某個時間執行一些操作。
    創建可等待的計時器:
    HANDLE CreateWaitableTimer(
        PSECURITY_ATTRIBUTES psa,
        BOOL bManualReset,//TRUE,手動重置計時器,FALSE,自動重置計時器;
        PCTSTR pszName);

    當手動重置計時器被觸發的時候,正在等待該計時器的所有線程都會變成可調度狀態。當自動重置計時器被觸發的時候,只有一個正在等待該計時器大的線程變成可調度狀態。
    在創建的時候,可等待的計時器對象總是處於未觸發狀態。調用一下函數來處罰計時器:
    BOOL SetWaitableTimer(
        HANDLE hTimer,
        const LARGE_INTEGER *pDueTime,//計時器第一次觸發的時間;
        LONG lPeriod,//第一次觸發之後,計時器應該以怎樣的頻度觸發;
        PTIMERAPCROUTINE pfnCompletionRoutine,//APC調用相關
        PVOID pvArgToCompletionRoutine,
        BOOL bResume);

    取消計時器,這樣計時器就永遠不會觸發了,除非再調用SetWaitableTimer來對它進行重置:
    BOOL CancelWaitableTimer(HANDLE hTimer);

    可等待計時器和用戶計時器(通過SetTimer函數來設置)的最大區別在於用戶計時器需要在應用程序中使用大量的用戶界面基礎設施,從而消耗更多的資源。此外可等待計時器是內核對象,這意味着它們不僅可以在多個線程間共享,而且可以具備安全性。
6.信號量內核對象
    信號量內核對象用來對資源進行計數。與其它所有內核對象相同,它們也包含一個使用計數,但他們還包含另外兩個32位值:一個最大資源計數和一個當前資源計數。
    信號量的規則如下:

  • 如果當前資源計數大於0,那麼信號量處於觸發狀態;
  • 如果當前資源計數等於0,那麼信號量處於未觸發狀態;
  • 系統絕對不會讓當前資源計數變爲負數;
  • 當前資源計數絕對不會大於最大資源計數;   

    創建信號量內核對象:
    HANDLE CreateSemaphore(
        PSECURITY_ATTRIBUTE psa,
        LONG lInitialCount,//資源初始計數
        LONG lMaximumCount,//資源的最大計數
        PCTSTR pszName);

    如果等待函數發現信號量當前資源計數爲0(信號量處於未觸發狀態),那麼系統會讓調用線程進入等待狀態。當另一個線程將信號量的當前資源計數遞增時,系統會記得那個還在等待的線程,使它們變成可調度狀態(並相應遞減當前資源計數)。
    線程通過調用ReleaseSemaphore來遞增心好像的當前資源計數:
    BOOL ReleaseSemaphore(
        HANDLE hSemaphore,
        LONG lReleaseCount,//把這個值加到資源計數上,通常爲1
        PLONG plPreviousCount);//返回當前資源計數的原始值;

7.互斥量內核對象
    互斥量內核對象用來確保一個線程獨佔對一個資源的訪問。互斥量與關鍵段的行爲完全相同。但是,互斥量是內核對象,而關鍵段是用戶模式下的同步對象。
    互斥量的規則:

  • 如果線程ID爲0(無效線程ID),那麼該互斥量不爲任何線程所佔用,它處於觸發狀態;
  • 如果線程ID爲非零值,那麼有一個線程已經佔用了該互斥量,它處於未觸發狀態;
  • 與所有其它內核對象不同,操作系統對互斥量進行了特殊處理,允許它們違反一些常規的規則;

    創建一個互斥量:
    HANDLE CreateMutex(
        PSECURITY_ATTRIBUTES psa,
        BOOL bInitialOwner,//指定是否爲調用線程所有
        PCTSTR pszName);

    當目前佔用訪問權的線程不再需要訪問資源的時候,它必須調用ReleaseMutex函數來釋放互斥量:
    BOOL ReleaseMutex(HANDLE hMutex);

   

特徵

互斥量

關鍵段

性能

是否能跨進程使用

聲明

HANDLE hmtx;

CRITICAL_SECTION cs;

初始化

hmtx = CreateMutex (NULL, FALSE, NULL);

InitializeCriticalSection(&cs);

清理

CloseHandle(hmtx);

DeleteCriticalSection(&cs);

無限等待

WaitForSingleObject (hmtx, INFINITE);

EnterCriticalSection(&cs);

0等待

WaitForSingleObject (hmtx, 0);

TryEnterCriticalSection(&cs);

任意時間長度的等待

WaitForSingleObject (hmtx, dwMilliseconds);

不支持

釋放

ReleaseMutex(hmtx);

LeaveCriticalSection(&cs);

是否能同時等待其它

內核對象

是 (使用WaitForMultipleObjects 或類似函數)

                                          表1:互斥量和關鍵段比較
8.線程同步對象速查表

對象

何時處於未觸發狀態

何時處於觸發狀態

成功等待的副作用

進程

當進程仍在運行的時候

當進程終止運行時(ExitProcess,

Te rminateProcess)

線程

當線程仍在運行時

當線程終止運行時(ExitThread,

TerminateThread)

作業

當作業尚未超時的時候

當作業超時的時候

文件

當I / O請求正在處理時

當I / O請求處理完畢時

控制檯輸入

不存在任何輸入

當存在輸入時

文件修改通知

沒有任何文件被修改

當文件系統發現修改時

重置通知

自動重置事件

ResetEvent , PulseEvent或等待成功

當調用SetEvent / PulseEvent時

重置事件

手動重置事件

ResetEvent或PulseEvent

當調用SetEvent / PulseEvent時

自動重置等待計時器

CancelWaitableTimer或等待成功

當時間到時(SetWaitableTimer)

重置定時器

手動重置等待計時器

CancelWaitableTimer

當時間到時(SetWaitableTimer)

信號量

等待成功

當數量> 0時(ReleaseSemaphore)

數量遞減1

互斥對象

等待成功

當未被線程擁有時(Release互斥對象)

將所有權賦予線程

關鍵代碼段(用戶模式)

等待成功((Try)EnterCriticalSection)

當未被線程擁有時(LeaveCriticalSection)

將所有權賦予線程

SRWLock
(用戶模式)
等待成功的時候
(AcquireSRWLock(Exclusive))
不爲線程佔用的時候
(ReleaseSRWLock(Exclusive))
把所有權交給線程
條件變量
(用戶模式)
等待成功地時候
(SleepConditionVariable*)
被喚醒的時候
(Wake(All)ConditionVariable)
沒有
表2:內核對象與線程同步










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