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