第三章   快跑和等待

資源網(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)遲鈍,喪失重繪能力。

2MsgWaitForMultipleObjects()不允許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