Windows高階程式設計之執行緒與核心物件的同步
使用者方式同步的優點是它的同步速度非常快。如果強調執行緒的執行速度,那麼首先應該確定使用者方式的執行緒同步機制是否適合需要。
使用者方式執行緒同步機制的侷限性:
1、互鎖函式家族只能在單值上執行
2、關鍵程式碼段只能對單個程序中的執行緒實施同步
3、關鍵程式碼段容易陷入死鎖狀態,因為無法設定超時值。
核心物件機制的適應性遠遠優於使用者方式機制。不足之處在於速度較慢,並且需要從使用者方式轉為核心方式
當程序正在執行的時候,程序核心物件處於未通知狀態,當程序終止執行的時候,它就變為已通知狀態。程序核心物件中是個布林值,當物件建立時,該值被初始化為 FA L S E(未通知狀態) 。當程序終止執行時,作業系統自動將對應的物件布林值改為T R U E,表示該物件已經得到通知。
即執行緒核心物件總是在未通知狀態中建立。當執行緒終止執行時,作業系統會自動將執行緒物件的狀態改為已通知狀態。
下面的核心物件可以處於已通知狀態或者未通知狀態
程序/檔案修改通知/執行緒/事件/作業/可等待定時器/檔案/信標/控制檯輸入/互斥物件
執行緒可以使自己進入等待狀態,直到一個物件變為已通知狀態。注意,用於控制每個物件的已通知/未通知狀態的規則要根據物件的型別而定。
當執行緒等待的物件處於未通知狀態中時,這些執行緒不可排程。但是一旦物件變為已通知狀態,執行緒看到該標誌變為可排程狀態,並且很快恢復執行。
9.1 等待函式
等待函式可使執行緒自願進入等待狀態,直到一個特定的核心物件變為已通知狀態為止。這些等待函式中最常用的是WaitForSingleObject:
DWORD WaitForSingleObject(HANDLE hObject,DWORD dwMilliseconds);
第一個引數hObject標識一個能夠支援被通知 /未通知的核心物件(前面列出的任何一種物件都適用) 。第二個引數dwMilliseconds允許該執行緒指明,為了等待該物件變為已通知狀態,它將等待多長時間。INFINITE表示無限等待
WaitForSingleObject(hProcess,INFINITE);呼叫函式準備等待到hProcess控制代碼標識的程序終止執行為止:
WaitForSingleObject的返回值能夠指明呼叫執行緒為什麼再次變為可排程狀態。如果執行緒等待的物件變為已通知狀態,那麼返回值是WAIT_OBJECT_0。如果設定的超時已經到期,則返回值是WAIT_TIMEOUT。如果將一個錯誤的值(如一個無效控制代碼)傳遞給WaitForSingleObject,那麼返回值將是WAIT_FAILED。
DWORD WaitForMultipleObjects(DWORD dwCount,CONST HANDLE *phObjects,BOOL fWaitAll,DWORD dwMilliseconds);
WaitForMultipleObjects允許呼叫執行緒同時檢視若干個核心物件的已通知狀態。
dwCount引數用於指明想要讓函式檢視的核心物件的數量。這個值必須在 1與MAXIMUM_WAIT_OBJECTS(在Windows標頭檔案中定義為64)之間。phObjects引數是指向核心物件控制代碼的陣列的指標。
fWaitAll為TRUE表示讓執行緒進入等待狀態,直到所有指定的核心物件都變為已通知狀態。
為FALSE表示讓執行緒進入等待狀態,直到指定核心物件中的任何一個變為已通知狀態。
WaitForMultipleObjects的返回值為WAIT_FAILED和WAIT_TIMEOUT。如果fWaitAll為TRUE,同時所有物件均變為已通知狀態,那麼返回值是WAIT_OBJECT_0。如果fWaitAll為FALSE,那麼一旦任何一個物件變為已通知狀態,該函式便返回,返回值是WAIT_OBJECT_0與(WAIT_OBJECT_0+dwCount-1)之間的一個值。換句話說,如果返回值不是WAIT_TIMEOUT,也不是WAIT_FAILED,那麼應該從返回值中減去WAIT_OBJECT_0。產生的數字是作為第二個引數傳遞給WaitForMultipleObjects的控制代碼陣列中的索引。該索引說明哪個物件變為已通知狀態。
如果為fWaitAll引數傳遞FALSE,WaitForMultipleObjects就從索引0開始向上對控制代碼陣列進行掃描,同時已通知的第一個物件終止等待狀態。
9.2 成功等待的副作用
成功地呼叫WaitForSingleObject和WaitForMultipleObjects,實際上會改變物件的狀態。成功地呼叫是指函式發現物件已經得到通知並且返回一個相對於WAIT_OBJECT_0的值,即物件的狀態已經改變。
例子:兩個執行緒以完全相同的方式來呼叫WaitForMultipleObjects:
HANDLE h[2];
h[0] = hAutoResetEvent1; //Initially nonsignaled
h[1] = hAutoResetEvent2; //Initially nonsignaled
WaitForMultipleObjects(2,h,TRUE,INFINITE);
當WaitForMultipleObjects函式被呼叫時,兩個事件都處於未通知狀態,這就迫使兩個執行緒都進入等待狀態。然後hAutoResetEvent1物件變為已通知狀態。兩個執行緒都發現,該事件已經變為已通知狀態,但是它們都無法被喚醒,因為hAutoResetEvent2仍然處於未通知狀態。由於兩個執行緒都沒有等待成功,因此沒有對hAutoResetEvent1物件產生任何副作用。接著,hAutoResetEvent2變為已通知狀態。這時,兩個執行緒中的一個發現,兩個物件都變為已通知狀態。等待取得了成功,兩個事件物件均被置為未通知狀態,該執行緒變為可排程的執行緒。但是另一個執行緒的情況如何呢?它將繼續等待,直到它發現兩個事件物件都處於已通知狀態。儘管它原先發現hAutoResetEvent1處於已通知狀態,但是現在它將該物件視為未通知狀態。
問題在於多個執行緒等待單個核心物件,那麼當該物件變成已通知狀態時,系統究竟決定喚醒哪個執行緒呢?Microsoft採用的公平演算法:意味著如果多個執行緒正在等待,那麼每當物件變為已通知狀態時,每個執行緒都應該得到它自己的被喚醒的機會。這意味著執行緒的優先順序不起任何作用,即高優先順序執行緒不一定得到該物件。這還意味著等待時間最長的執行緒不一定得到該物件。同時得到物件的執行緒有可能反覆迴圈,並且再次得到該物件。
9.3 事件核心物件
在所有的核心物件中,事件核心物件是個最基本的物件。它們包含一個使用計數(與所有核心物件一樣) ,一個用於指明該事件是個自動重置的事件還是一個人工重置的事件的布林值,另一個用於指明該事件處於已通知狀態還是未通知狀態的布林值。
事件能夠通知一個操作已經完成。有兩種不同型別的事件物件。一種是人工重置的事件,另一種是自動重置的事件。當人工重置的事件得到通知時,等待該事件的所有執行緒均變為可排程執行緒。當一個自動重置的事件得到通知時,等待該事件的執行緒中只有一個執行緒變為可排程執行緒。
當一個執行緒執行初始化操作,然後通知另一個執行緒執行剩餘的操作時,事件使用得最多。事件初始化為未通知狀態,然後,當該執行緒完成它的初始化操作後,它就將事件設定為已通知狀態。這時,一直在等待該事件的另一個執行緒發現該事件已經得到通知,因此它就變成可排程執行緒。
HANDLE CreateEvent(PSECURITY_ATTRIBUTES psa,BOOL fManualReset,BOOL fInitialState,PCTSTR pszName);
fManualReset引數是個布林值,它能夠告訴系統是建立一個人工重置的事件(TRUE)還是建立一個自動重置的事件(FALSE) 。fInitialState引數用於指明該事件是要初始化為已通知狀態(TRUE)還是未通知狀態(FALSE) 。當系統建立事件物件後,CreateEvent就將與程序相
關的控制代碼返回給事件物件。其他程序中的執行緒可以獲得對該物件的訪問權,方法是使用在pszName引數中傳遞的相同值。使用繼承性,使用DuplicateHandle函式等來呼叫CreateEvent,或者呼叫OpenEvent,在pszName引數中設定一個與呼叫CreateEvent時設定的名字相匹配的名字:
HANDLE OpenEvent(DWORD fdwAccess,BOOL fInherit,PCTSTR pszName);
當不再需要事件核心物件時,應該呼叫CloseHandle函式。
一旦事件已經建立,就可以直接控制它的狀態。當呼叫SetEvent時,可以將事件改為已通知狀態:
BOOL SetEvent(HANLDE hEvent);
當呼叫ResetEvent函式時,可以將該事件改為未通知狀態:
BOOL ResetEvent(HANDLE hEvent);
Microsoft為自動重置的事件定義了應該成功等待的副作用規則,即當執行緒成功地等待到該物件時,自動重置的事件就會自動重置到未通知狀態。這就是自動重置的事件如何獲得它們的名字的方法。通常沒有必要為自動重置的事件呼叫ResetEvent函式,因為系統會自動對事件進行重置。但是,Microsoft沒有為人工重置的事件定義成功等待的副作用。
//Create a global handle to a manual-reset,nonsignaled event
HANLDE g_hEvent;
int WINAPI WinMain(...){
//Create the manual-reset,nonsignaled event
g_hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
//Spawn 3 new threads
HANDLE hThread[3];
DWORD dwThreadID;
hThread[0] = _beginthreadex(NULL,0,WordCount,NULL,0,&dwThreadID);
hThread[1] = _beginthreadex(NULL,0,SpellCheck,NULL,0,&dwThreadID);
hThread[2] = _beginthreadex(NULL,0,GrammarCheck,NULL,0,&dwThreadID);
OpenFileAndReadContentsIntoMemory(..)
//Allow all 3 thread to access the memory.
SetEvent(g_hEvent);
}
DWORD WINAPI WordCount(PVOID pvParam){
//Wait until the file's data is in memory
WaitForSingleObject(g_hEvent,INFINITE);
//Access the memory block
...
return(0);
}
如果你使用自動重置的事件而不是人工重置的事件,那麼應用程式的行為特性就有很大的差別。當主執行緒呼叫SetEvent之後,系統只允許一個輔助執行緒變成可排程狀態。
注意,當使用自動重置事件時,如果每個輔助執行緒均以讀 /寫方式訪問記憶體塊,那麼就不會產生任何問題,這些執行緒將不再被要求將資料視為只讀資料。
BOOL PulseEvent(HANLDE hEvent);
PulseEvent函式使得事件變為已通知狀態,然後立即又變為未通知狀態,這就像在呼叫SetEvent後又立即呼叫ResetEvent函式一樣。如果在人工重置的事件上呼叫PulseEvent函式,那
麼在發出該事件時,等待該事件的任何一個執行緒或所有執行緒將變為可排程執行緒。如果在自動重置事件上呼叫PulseEvent函式,那麼只有一個等待該事件的執行緒變為可排程執行緒。如果在發出事件時沒有任何執行緒在等待該事件,那麼將不起任何作用。
9.4 等待定時器核心物件
等待定時器是在某個時間或按規定的間隔時間發出自己的訊號通知的核心物件。它們通常用來在某個時間執行某個操作。
HANDLE CreateWaitableTimer(PSECURITY_ATTRIBUTES psa,BOOL fManualReset,PCTSTR pszName);
fManualReset引數用於指明人工重置的定時器或自動重置的定時器。當發出人工重置的定時器訊號通知時,等待該定時器的所有執行緒均變為可排程執行緒。當發出自
動重置的定時器訊號通知時,只有一個等待的執行緒變為可排程執行緒。
CreateWaitableTimer建立等待定時器。
程序可以獲得它自己的與程序相關的現有等待定時器的控制代碼,方法是呼叫OpenWaitableTimer函式:
HANLDE OpenWaitableTimer(DWORD dwDesireAccess,BOOL hInheritHandle,PCTSTR pszName);
等待定時器物件總是在未通知狀態中建立。必須呼叫SetWaitableTimer函式來告訴定時器你想在何時讓它成為已通知狀態:
BOOL SetWaitableTimer(HANDLE hTimer,const LARGE_INTEGER *pDueTime,LONG lPeriod,PTIMERAPCROUTINE pfnCompletionRoutine,PVOID pvArgToCompletionRoutine,BOOL fResume);
hTimer引數用於指明你要設定的定時器,pDueTime和lPeriod兩個引數是一道使用的,pDueTimer引數用於指明定時器何時應該第一次報時,lPeriod引數則用於指明此後定時器應該間隔多長時間報時一次。
SetWaitableTimer希望傳遞給它的時間始終都採用世界協調時(U T C)的時間。
//下面程式碼用於將定時器的第一次報時的時間設定在2 0 0 2年1月1日的下午1點鐘,然後每隔6小時報時一次:
//Declare our local variable
HANDLE hTimer;
SYSTEMTIME st;
FILETIME ftLocal,ftUTC;
LARGE_INTEGER liUTC;
//Create an auto-reset timer;
hTimer = CreateWaitable(NULL,FALSE,NULL);
//First signaling is at 202-1-1 1:00PM(Local Time)
st.wYear = 2002;st.wMonth = 1;st.wDayOfWeek = 0;st.wDay=1;st.wHour=13;st.wMinute=0;st.wSecond=0;st.wMilliseconds=0;
SystemTimeToFileTime(&st,&ftLocal);
//Convert local time to UTC time
LocalFileTimeToFileTime(&ftLocal,&ftUTC);
//Convert FILETIME to LARGE_INTEGER because of different alignment
liUTC.LowPart = ftUTC.dwLowDateTime;
liUTC.HighPart = ftUTC.dwHighDateTime;
//Set the Timer
SetWaitableTimer(hTimer,&liUTC,6*60*60*1000,NULL,NULL,FALSE);
雖然FILETIME和LARGE_INTEGER結構採用相同的二進位制格式,但是這兩個結構的調整要求不同。所有FILETIME結構的地址必須從一個3 2位的邊界開始,而所有LARGE_INTEGER結構的地址則必須從6 4位的邊界開始。如果不設定定時器應該第一次報時的絕對時間,也可以讓定時器在一個相對於呼叫SetWaitableTimer的時間進行報時。只需要在pDueTime引數中傳遞一個負值。傳遞的值必須是以100ns為間隔。
通常情況下,你可能想要一個一次報時的定時器,它只是發出一次報時訊號,此後再也不發出報時訊號。若要做到這一點,只需要為lPeriod引數傳遞0即可。然後可以呼叫CloseHandle
函式,關閉定時器,或者再次呼叫SetWaitableTimer函式,重新設定時間,為它規定一個需要遵循的新條件。SetWaitableTimer的最後一個引數是fResume,它可以用於支援暫停和恢復的計算機。通常可以為該引數傳遞FALSE,
BOOL CancelWaitableTimer(HANDLE hTimer);
用於取出定時器的控制代碼並將它撤消,除非接著呼叫SetWaitableTimer函式以便重新設定定時器,否則定時器決不會進行報時。如果想要改變定時器的報時條件,不必在呼叫SetWaitableTimer函式之前呼叫CancelWaitableTimer函式。每次呼叫SetWaitableTimer函式,都會在設定新的報時條件之前撤消定時器原來的報時條件。
9.4.1 讓等待定時器給APC項排隊
Microsoft還允許定時器給在定時器得到通知訊號時呼叫SetWaitableTimer函式的執行緒的非同步過程呼叫(APC)進行排隊。
一般來說,當呼叫SetWaitableTimer函式時,你將同時為pfnCompletionRoutine和pvArgCompletionRoutine引數傳遞N U L L。當SetWaitableTimer函式看到這些引數的N U L L時,它
就知道,當規定的時間到來時,就向定時器發出通知訊號。但是,如果到了規定的時間,你願意讓定時器給一個A P C排隊,那麼你必須傳遞定時器A P C例程的地址,例程必須自己實現,類似於下面的形式:
VOID APIENTRY TimerAPCRoutine(PVOID pvArgToCompletionRoutine,DWORD dwTimerLowValue,DWORD dwTimerHighValue){
//do whatever you want here
}
該函式可以在定時器報時的時候由呼叫SetWaitableTimer函式的同一個執行緒來呼叫,但是隻有在呼叫執行緒處於待命狀態下才能呼叫。換句話說,該執行緒必須正在SleepEx/WaitForSingleObjectEx/WaitForMultipleObjectsEx,MsgWaitForMultipleObjectsEx/SingleObject-AndWait等函式的呼叫中等待。如果該執行緒不在這些函式中的某個函式中等待,系統將不給定時器APC例程排隊。這可以防止執行緒的APC佇列中塞滿定時器APC通知,這會浪費系統中的大量記憶體。當定時器報時的時候,如果你的執行緒處於待命的等待狀態中,系統就使你的執行緒呼叫回撥例程。回撥例程的第一個引數的值與你傳遞給SetWaitableTimer函式的pvArgToCompletionRoutine引數的值是相同的,剩餘的兩個引數dwTimerLowValue和dwTimerHighValue用於指明定時器何時報時。
只有當所有的A P C項都已經處理之後,待命的函式才會返回。因此,必須確保定時器再次變為已通知狀態之前,TimerAPCRoutine函式完成它的執行,這樣,A P C項的排隊速度就不會比它被處理的速度快。
最後要說明的是,執行緒不應該等待定時器的控制代碼,也不應該以待命的方式等待定時器。
9.4.2 定時器的鬆散特性
定時器常常用於通訊協議中。客戶機通常要同時與許多伺服器進行通訊。如果你為每個請求建立一個定時器核心物件,那麼系統的執行效能就會受到影響。可以設想,對於大多數應用程式來說,可以建立單個定時器物件,並根據需要修改定時器報時的時間。
定時器報時時間的管理方法和定時器時間的重新設定是非常麻煩的,只有很少的應用程式採用這種方法。使用CreateTimerQueueTimer函式
雖然定時器能夠給APC項進行排隊是很好的,但是目前編寫的大多數應用程式並不使用APC,它們使用I / O完成埠機制。例如:執行緒池中(由一個 I / O完成埠負責管理)有一個執行緒,它按照特定的定時器間隔醒來。但是,等待定時器沒有提供這個方法。為了做到這一點,我建立了一個執行緒,它的唯一工作是設定而後等待一個等待定時器。當定時器變為已通知狀態時,執行緒就呼叫PostQueuedCompletionStatus函式,將一個事件強加給執行緒池中的一個執行緒。
SetWaitableTimer與SetTimer的區別是:使用者定時器需要在應用程式中設定許多附加的使用者介面結構,這使定時器變得資源更加密集。另外,等待定時器屬於核心物件,這意味著它們可以供多個執行緒共享,並且是安全的。使用者定時器能夠生成WM_TIMER訊息,這些訊息將返回給呼叫SetTimer(用於回撥定時器)的執行緒和建立視窗(用於基於視窗的定時器)的執行緒。因此,當用戶定時器報時的時候,只有一個執行緒得到通知。另一方面,多個執行緒可以在等待定時器上進行等待,如果定時器是個人工重置的定時器,則可以排程若干個執行緒。
如果要執行與使用者介面相關的事件,以便對定時器作出響應,那麼使用使用者定時器來組織程式碼結構可能更加容易些,因為使用等待定時器時,執行緒必須既要等待各種訊息,又要等待內
核對象。最後,運用等待定時器,當到了規定時間的時候,更有可能得到通知。
9.5 信標核心物件
信標核心物件用於對資源進行計數。它們與所有核心物件一樣,包含一個使用數量,但是它們也包含另外兩個帶符號的3 2位值,一個是最大資源數量,一個是當前資源數量。最大資源
數量用於標識信標能夠控制的資源的最大數量,而當前資源數量則用於標識當前可以使用的資源的數量。
比如說,我正在開發一個伺服器程序,在這個程序中,我已經分配了一個能夠用來存放客戶機請求的緩衝區。我對緩衝區的大小進行了硬編碼,這樣它每次最多能夠存放5個客戶機請求。如果5個請求尚未處理完畢時,一個新客戶機試圖與伺服器進行聯絡,那麼這個新客戶機的請求就會被拒絕,並出現一個錯誤,指明伺服器現在很忙,客戶機應該過些時候重新進行聯絡。當我的伺服器程序初始化時,它建立一個執行緒池,裡面包含 5個執行緒,每個執行緒都準備在客戶機請求到來時對它進行處理。開始時,沒有客戶機提出任何請求,因此我的伺服器不允許執行緒池中的任何執行緒成為可排程執行緒。使用信標,就能夠很好地處理對資源的監控和對執行緒的排程,最大資源數量設定為 5,當前資源數量最初設定為0,因為沒有客戶機提出任何請求。當客戶機的請求被接受時,當前資源數量就遞增,當客戶機的請求被提交給伺服器的執行緒池時,當前資源數量就遞減。
信標的使用規則如下:
• 如果當前資源的數量大於0,則發出信標訊號。
• 如果當前資源數量是0,則不發出信標訊號。
• 系統決不允許當前資源的數量為負值。
• 當前資源數量決不能大於最大資源數量。
HANDLE CreateSemaphore(PSECURITY_ATTRIBUTE psa,LONG lInitialCount,LONG lMaxmumCount,PCTSTR pszName);
HANDLE OpenSemaphore(DWORD fdwAccess,BOOL bInheritHandle,PCTSTR pszName);獲得它自己的程序與現有信標相關的控制代碼
lMaxmumCount引數用於告訴系統,應用程式處理的最大資源數量是多少。lInitialCount引數用於指明開始時(當前)這些資源中有多少可供使用。
通過呼叫等待函式,傳遞負責保護資源的信標的控制代碼,執行緒就能夠獲得對該資源的訪問權。從內部來說,該等待函式要檢查信標的當前資源數量,如果它的值大於0(信標已經發出訊號) ,那麼計數器遞減1,呼叫執行緒保持可排程狀態。信標的出色之處在於它們能夠以原子操作方式來執行測試和設定操作,這就是說,當向信標申請一個資源時,作業系統就要檢查是否有這個資源可供使用,同時將可用資源的數量遞減,而不讓另一個執行緒加以干擾。只有當資源數量遞減後,系統才允許另一個執行緒申請對資源的訪問權。
BOOL ReleaseSemaphore(HANDLE hsem,LONG lReleaseCount,PLONG plPreviousCount);
該函式只是將lReleaseCount中的值新增給信標的當前資源數量。該函式也能夠在它的*plPreviousCount中返回當前資源數量的原始值。
9.6 互斥物件核心物件
互斥物件(mutex)核心物件能夠確保執行緒擁有對單個資源的互斥訪問權。互斥物件包含一個使用數量,一個執行緒I D和一個遞迴計數器。互斥物件的行為特性與關鍵程式碼段相同,但是互斥物件屬於核心物件,而關鍵程式碼段則屬於使用者方式物件。這意味著互斥物件的執行速度比關鍵程式碼段要慢。但是這也意味著不同程序中的多個執行緒能夠訪問單個互斥物件,並且這意味著執行緒在等待訪問資源時可以設定一個超時值。ID用於標識系統中的哪個執行緒當前擁有互斥物件,遞迴計數器用於指明該執行緒擁有互斥物件的次數。通常來說,它們用於保護由多
個執行緒訪問的記憶體塊。
互斥物件的使用規則如下:
• 如果執行緒I D是0(這是個無效I D) ,互斥物件不被任何執行緒所擁有,並且發出該互斥物件的通知訊號。
• 如果I D是個非0數字,那麼一個執行緒就擁有互斥物件,並且不發出該互斥物件的通知訊號。
• 與所有其他核心物件不同, 互斥物件在作業系統中擁有特殊的程式碼,允許它們違反正常的規則 。
HANDLE CreateMutex(PSECURITY_ATTRIBUTES psa,BOOL fInitialOwner,PCTSTR pszName);
HANDLE OpenMutex(DWORD fdwAccess,BOOL bInheritHandle,PCTSTR pszName);
fInitialOwner引數用於控制互斥物件的初始狀態。如果傳遞 FA L S E(這是通常情況下傳遞的值) ,那麼互斥物件的I D和遞迴計數器均被設定為0。這意味著該互斥物件沒有被任何執行緒所擁有,因此要發出它的通知訊號。如果為fInitialOwner引數傳遞T R U E,那麼該物件的執行緒I D被設定為呼叫執行緒的I D,遞迴計數器被設定為1。由於I D是個非0數字,因此該互斥物件開始時不發出通知訊號。
通過呼叫一個等待函式,並傳遞負責保護資源的互斥物件的控制代碼,執行緒就能夠獲得對共享資源的訪問權。在內部,等待函式要檢查執行緒的 I D,以瞭解它是否是0(互斥物件發出通知信
號) 。如果執行緒I D是0,那麼該執行緒I D被設定為呼叫執行緒的I D,遞迴計數器被設定為1,同時,呼叫執行緒保持可排程狀態。如果等待函式發現I D不是0(不發出互斥物件的通知訊號) ,那麼呼叫執行緒便進入等待狀態。系統將記住這個情況,並且在互斥物件的 I D重新設定為0時,將執行緒I D設定為等待執行緒的I D,將遞迴計數器設定為1,並且允許等待執行緒再次成為可排程執行緒。
BOOL ReleaseMutex(HANDLE hMutex);釋放互斥物件,該函式將物件的遞迴計數器遞減 1
9.8 其他的執行緒同步函式
9.8.1 非同步裝置I / O
非同步裝置I / O使得執行緒能夠啟動一個讀操作或寫操作,但是不必等待讀操作或寫操作完成。
裝置物件是可以同步的核心物件,這意味著可以呼叫WaitForSingleObject函式,傳遞檔案、套接字和通訊埠的控制代碼。當系統執行非同步I / O時,裝置物件處於未通知狀態。一旦操作完成,系統就將物件的狀態改為已通知狀態,這樣,該執行緒就知道操作已經完成。此時,該執行緒就可以繼續執行。
執行緒也可以呼叫WaitForInputIdle來終止自己的執行
DWORD WaitForInputIdle(HANDLE hProcess,DWORD dwMilliseconds);
該函式將一直處於等待狀態,直到hProcess標識的程序在建立應用程式的第一個視窗的執行緒中已經沒有尚未處理的輸入為止。當父程序的執行緒呼叫CreateProcess時,該父程序的執行緒將在子程序初始化時繼續執行。父程序的執行緒可能需要獲得子程序建立的視窗的控制代碼。如果父程序的執行緒想要知道子程序何時完成初始化,唯一的辦法是等待,直到子程序不再處理任何輸入為止。因此,當呼叫CreateProcess後,父程序的執行緒就呼叫WaitForInputIdle。
編寫1 6位Windwos應用程式的程式設計人員常常要面對這個問題。應用程式想要將訊息顯示在視窗中,但是它並不確切知道視窗何時建立完成、作好接受訊息的準備。
9.8.3 MsgWaitForMultipleObjects( Ex )
此函式用於執行緒等待它自己的訊息。
DWORD MsgWaitForMultipleObjects(DWORD dwCount,PHANDLE phObjects,BOOL fWaitAll,DWORD dwMilliseconds,DWORD dwWakeMask);
DWORD MsgWaitForMultipleObjectsEx(DWORD dwCount,PHANDLE phObjects,BOOL fWaitAll,DWORD dwWakeMask,DWORD dwFlags);
這些函式與WaitForMultipleObjects函式十分相似。差別在於它們允許執行緒在核心物件變成已通知狀態或視窗訊息需要排程到呼叫執行緒建立的視窗中時被排程。
建立視窗和執行與使用者介面相關的任務的執行緒,應該呼叫MsgWaitForMultipleObjectsEx函式,而不應該呼叫WaitForMultipleObjects函式,因為後面這個函式將使執行緒的使用者介面無法對使用者作出響應。
9.8.5 SingleObjectAndWait
SingleObjectAndWait函式用於在單個原子方式的操作中發出關於核心物件的通知並等待另一個核心物件。
DWORD SingalObjectAndWait(HANDLE hObjectToSignal,HANLDE hObjectToWaitOn,DWORD dwMilliseconds,BOOL fAlertable);