第三章 快跑和等待
資源網(wǎng)絡(luò)收集 感謝原創(chuàng)者
轉(zhuǎn)自http://blog.sina.com.cn/s/blog_5678943c0100d4po.html
本章回答了如下幾個問題:
◆ 如何等待線程序的結(jié)束?為什么通過查詢方式判斷線程是否終止是“看似空閑實(shí)則忙碌”?這有何不妥?怎樣改進(jìn),才能使等待變得有效率起來?
◆ 如何等待一個線程的結(jié)束?如何等待多個線程的結(jié)束?推而廣之,如何等待一個核心對象?如何等待多個核心對象?
◆ 在GDI線程中等待需要特別注意些什么?怎么做?對于主線程來說,收到WM_QUIT消息時能否立即終止線程?為什么?該怎么做?
◆ 在多對象等待中,對象數(shù)組的處理需要注意些什么?比如,正在等待的某個對象被其它線程刪除了、修改了,或要新添一個需要等待的核心對象,怎么處理?
看似閑暇卻忙碌(Busy Waiting)
操作系統(tǒng)沒有能力分辨哪個線程的工作是有用的,哪個線程的工作是比較沒用的,所以每個線程獲得一律平等的CPU時間。通過反復(fù)調(diào)用GetExitCodeThread()查看線程是否終結(jié)會白白浪費(fèi)CPU時間。
記住:讓等待變得有效率,是一件很重要的事。
等待一個線程的結(jié)束
DWORD WaitForSingleObject(
HANDLE hHandle, // handle to object to wait for
DWORD dwMilliseconds // time-out interval in milliseconds
);
此函數(shù)返回成功的三個因素:
1) 等待的目標(biāo)(核心對象)變成激發(fā)狀態(tài),返回WAIT_OBJECT_0;
2) 核心對象變成激發(fā)狀態(tài)前,等待時間到了,返回WAIT_TIMEOUT;
3) 如果一個擁有MUTEX(互斥器)的線程結(jié)束前沒有釋放MUTEX,則傳回WAIT_ABANDONED。
如何檢查核心對象是否處于被激發(fā)狀態(tài)?
將WaitForSingleObject()中的時間超時設(shè)為0,如果對象處于激發(fā)狀態(tài),立即返回WAIT_OBJECT_0,否則返回WAIT_TIMEOUT。
什么是一個被激發(fā)的對象?
當(dāng)核心對象被激發(fā)時,會導(dǎo)致WaitForSingleObject()醒來。其它Wait()函數(shù)也是如此。
需要注意的是:醒來未必立即被調(diào)動,而只是從休眠態(tài)隊(duì)列排入就緒隊(duì)列,等待操作系統(tǒng)調(diào)度。
對于不同的核心對象,是否被激發(fā)含義不一:
對于線程/進(jìn)程,如果還在運(yùn)行,該對象就處于未激發(fā);如果運(yùn)行結(jié)束,則轉(zhuǎn)入激發(fā)狀態(tài)。
對于文件對象,如果一個I/O操作完成,該文件對象即處于激發(fā)狀態(tài);
對于事件,CreateEvent()初態(tài)為TRUE,或SetEvent()、PulseEvent(),事件為激發(fā)態(tài);
對于互斥器,如果未被任何進(jìn)程鎖定,該對象處于激發(fā)狀態(tài);
對于信號量,如果信號量的現(xiàn)值大于0,該對象處于激發(fā)狀態(tài);
對于磁盤改變通知,當(dāng)一個特定的磁盤子目錄中發(fā)生了一件特別的改變時,此對象被激發(fā)。
對于控制臺輸入,當(dāng)Console窗口的輸入緩沖區(qū)中有數(shù)據(jù)可用時,此對象被激發(fā)。
數(shù)個線程可以同時等待相同線程handle,當(dāng)該線程handle變成激發(fā)狀態(tài)時,所有等待的線程都會被喚醒。然而,其它核心對象可能只喚醒一個等待中的線程。到底是哪一種行為,得視你等待的是何種對象。
有些對象的激發(fā)狀態(tài)只能維持到一個等待中的線程被喚醒,有些象的激發(fā)狀態(tài)則能維持到它被明白地Reset了。(比較兩種事件對象:自動事件屬于前一類別,手工事件則屬于后一種)
Note:資源未被占有,即為激發(fā)狀態(tài)
關(guān)于目錄監(jiān)控
我們可以檢測一個特定目錄發(fā)生的變化,具體可使用下面的三個函數(shù):
HANDLE FindFirstChangeNotification(
LPCTSTR lpPathName, // pointer to name of directory to watch
BOOL bWatchSubtree, // flag for monitoring directory or directory tree
DWORD dwNotifyFilter // filter conditions to watch for
);
lpPathName定義被監(jiān)視的目錄所在;
bWatchSubtree定義是否監(jiān)視該目錄下的子目錄;
dwNotifyFilter定義具體監(jiān)視哪些變化,可以是如下六種變化(可多選):
FILE_NOTIFY_CHANGE_FILE_NAME :建立、刪除、文件改名
FILE_NOTIFY_CHANGE_DIR_NAME :建立或刪除一個目錄
FILE_NOTIFY_CHANGE_ATTRIBUTES :目錄及子目錄中的任何屬性改變
FILE_NOTIFY_CHANGE_SIZE :目錄及子目錄中的任何文件大小的改變
FILE_NOTIFY_CHANGE_LAST_WRITE :目錄及子目錄中的任何文件的最后寫入時間的改變
FILE_NOTIFY_CHANGE_SECURITY :目錄及子目錄中的任何屬性改變
FindFirstChangeNotification()函數(shù)的返回值可以作為等待函數(shù)中的等待對象,從而實(shí)現(xiàn)對特定目錄或子目錄的監(jiān)視。只要被監(jiān)視的某一種事件發(fā)生,等待函數(shù)返回。
等待函數(shù)返回后,應(yīng)用程序可以對此進(jìn)行響應(yīng)處理,調(diào)用FindNextChangeNotification()并繼續(xù)等待。
BOOL FindNextChangeNotification(
HANDLE hChangeHandle // handle to change notification to signal
);
等FindNextChangeNotification()函數(shù)成功返回后,應(yīng)用程序可以使用等待函數(shù)等待變化通知。
如果一個變化在FindFirstChangeNotification()調(diào)用后FindNextChangeNotification()調(diào)用前發(fā)生,操作系統(tǒng)記錄這個改變,等FindNextChangeNotification()執(zhí)行完,記錄改變立即激活一個等待它的等待函數(shù)。
沒使用等待函數(shù)前FindNextChangeNotification()不可使用超過1次。如果有改變等待處理,調(diào)用FindNextChangeNotification()可能會漏失一個改變通知。
如果要結(jié)束目錄監(jiān)視,使用FindCloseChangeNotification()。
BOOL FindCloseChangeNotification(
HANDLE hChangeHandle // handle to change notification to close
);
等待多個對象
Win32中可以等待多個對象,等待所有事件或者某一個事件被激發(fā)。
DWORD WaitForMultipleObjects(
DWORD nCount, // number of handles in the handle array
CONST HANDLE *lpHandles, // pointer to the object-handle array
BOOL fWaitAll, // wait flag
DWORD dwMilliseconds // time-out interval in milliseconds
);
在一個GDI程序中等待
如果需要在主消息循環(huán)里等待某個對象,如果只是使用WaitForSingleObjects()或WaitForMultipleObjects(),你根本就無法回到主消息循環(huán)里。這樣,應(yīng)用程序的用戶界面停止重繪,菜單工具條無法響應(yīng),總之,糟糕透了。
為了同時等待對象,并等待消息,可使用下面這個函數(shù):
DWORD MsgWaitForMultipleObjects(
DWORD nCount, // number of handles in the handle array
LPHANDLE pHandles, // pointer to the object-handle array
BOOL fWaitAll, // wait for all or wait for one
DWORD dwMilliseconds, // time-out interval in milliseconds
DWORD dwWakeMask // type of input events to wait for
);
前幾個參數(shù)和WaitForMultipleObjects()類似,最后一個參數(shù)定義欲觀察的用戶輸入消息。函數(shù)返回值為WAIT_OBJECT_0 + nCount時表示“消息到達(dá)隊(duì)列”。
消息處理的一個范本:
int rc = MsgWaitForMultipleObjects(nWaitCount,hWaitArray,FALSE,INFINITE,QS_ALLINPUT);
if (rc == WAIT_OBJECT_0 + nWaitCount)
{
while (PeekMessage(&msg,NULL,0,0,PM_REMOVE)
{
if (msg.message == WM_QUIT)
{
quit = true;
exitcode = msg.wParam;
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
……
}
使用MsgWaitForMultipleObjects()時需要注意的幾個地方
1.收到WM_QUIT之后,Windows仍然有消息傳遞給你。如果你要在收到WM_QUIT之后等待所有線程的結(jié)束,你必須繼續(xù)處理你的消息,否則窗口會變得反應(yīng)遲鈍,喪失重繪能力。
2.MsgWaitForMultipleObjects()不允許handles數(shù)組中有縫隙產(chǎn)生。所以,如果某個對象被激發(fā)了后,你應(yīng)該在下一次調(diào)用MsgWaitForMultipleObjects()前把handles數(shù)組重新整理、緊壓,而不是把該位置設(shè)置為NULL了事。
3.如果有另一個線程更改了對象數(shù)組,而那是你正等待的,那么你需要一種方法,強(qiáng)迫MsgWaitForMultipleObjects()返回,并重新開始,以包含這個新的 handle。