第三章   快跑和等待

資源網絡收集 感謝原創者

轉自http://blog.sina.com.cn/s/blog_5678943c0100d4po.html

本章回答了如下幾個問題:

   如何等待線程序的結束?為什么通過查詢方式判斷線程是否終止是“看似空閑實則忙碌”?這有何不妥?怎樣改進,才能使等待變得有效率起來?

   如何等待一個線程的結束?如何等待多個線程的結束?推而廣之,如何等待一個核心對象?如何等待多個核心對象?

   GDI線程中等待需要特別注意些什么?怎么做?對于主線程來說,收到WM_QUIT消息時能否立即終止線程?為什么?該怎么做?

   在多對象等待中,對象數組的處理需要注意些什么?比如,正在等待的某個對象被其它線程刪除了、修改了,或要新添一個需要等待的核心對象,怎么處理? 

看似閑暇卻忙碌(Busy Waiting

操作系統沒有能力分辨哪個線程的工作是有用的,哪個線程的工作是比較沒用的,所以每個線程獲得一律平等的CPU時間。通過反復調用GetExitCodeThread()查看線程是否終結會白白浪費CPU時間。

記住:讓等待變得有效率,是一件很重要的事。

 

等待一個線程的結束

DWORD WaitForSingleObject(

HANDLE hHandle,        // handle to object to wait for

DWORD dwMilliseconds   // time-out interval in milliseconds

);

此函數返回成功的三個因素:

1  等待的目標(核心對象)變成激發狀態,返回WAIT_OBJECT_0

2  核心對象變成激發狀態前,等待時間到了,返回WAIT_TIMEOUT;

3  如果一個擁有MUTEX(互斥器)的線程結束前沒有釋放MUTEX,則傳回WAIT_ABANDONED。 

如何檢查核心對象是否處于被激發狀態?

WaitForSingleObject()中的時間超時設為0,如果對象處于激發狀態,立即返回WAIT_OBJECT_0,否則返回WAIT_TIMEOUT。 

什么是一個被激發的對象?

當核心對象被激發時,會導致WaitForSingleObject()醒來。其它Wait()函數也是如此。

需要注意的是:醒來未必立即被調動,而只是從休眠態隊列排入就緒隊列,等待操作系統調度。 

對于不同的核心對象,是否被激發含義不一:

對于線程/進程,如果還在運行,該對象就處于未激發;如果運行結束,則轉入激發狀態。

對于文件對象,如果一個I/O操作完成,該文件對象即處于激發狀態;

對于事件,CreateEvent()初態為TRUE,或SetEvent()、PulseEvent(),事件為激發態;

對于互斥器,如果未被任何進程鎖定,該對象處于激發狀態;

對于信號量,如果信號量的現值大于0,該對象處于激發狀態;

對于磁盤改變通知,當一個特定的磁盤子目錄中發生了一件特別的改變時,此對象被激發。

對于控制臺輸入,當Console窗口的輸入緩沖區中有數據可用時,此對象被激發。 

數個線程可以同時等待相同線程handle,當該線程handle變成激發狀態時,所有等待的線程都會被喚醒。然而,其它核心對象可能只喚醒一個等待中的線程。到底是哪一種行為,得視你等待的是何種對象。

有些對象的激發狀態只能維持到一個等待中的線程被喚醒,有些象的激發狀態則能維持到它被明白地Reset了。(比較兩種事件對象:自動事件屬于前一類別,手工事件則屬于后一種) 

Note:資源未被占有,即為激發狀態

關于目錄監控

我們可以檢測一個特定目錄發生的變化,具體可使用下面的三個函數:

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定義被監視的目錄所在;

bWatchSubtree定義是否監視該目錄下的子目錄;

dwNotifyFilter定義具體監視哪些變化,可以是如下六種變化(可多選):

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()函數的返回值可以作為等待函數中的等待對象,從而實現對特定目錄或子目錄的監視。只要被監視的某一種事件發生,等待函數返回。

等待函數返回后,應用程序可以對此進行響應處理,調用FindNextChangeNotification()并繼續等待。 

BOOL FindNextChangeNotification(

HANDLE hChangeHandle   // handle to change notification to signal

);

FindNextChangeNotification()函數成功返回后,應用程序可以使用等待函數等待變化通知。

如果一個變化在FindFirstChangeNotification()調用后FindNextChangeNotification()調用前發生,操作系統記錄這個改變,等FindNextChangeNotification()執行完,記錄改變立即激活一個等待它的等待函數。

沒使用等待函數前FindNextChangeNotification()不可使用超過1次。如果有改變等待處理,調用FindNextChangeNotification()可能會漏失一個改變通知。

如果要結束目錄監視,使用FindCloseChangeNotification()。 

BOOL FindCloseChangeNotification(

HANDLE hChangeHandle   // handle to change notification to close

); 

等待多個對象

Win32中可以等待多個對象,等待所有事件或者某一個事件被激發。

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程序中等待

如果需要在主消息循環里等待某個對象,如果只是使用WaitForSingleObjects()或WaitForMultipleObjects(),你根本就無法回到主消息循環里。這樣,應用程序的用戶界面停止重繪,菜單工具條無法響應,總之,糟糕透了。

為了同時等待對象,并等待消息,可使用下面這個函數:

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

);

前幾個參數和WaitForMultipleObjects()類似,最后一個參數定義欲觀察的用戶輸入消息。函數返回值為WAIT_OBJECT_0 + nCount時表示消息到達隊列。

 

消息處理的一個范本:

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之后等待所有線程的結束,你必須繼續處理你的消息,否則窗口會變得反應遲鈍,喪失重繪能力。

2MsgWaitForMultipleObjects()不允許handles數組中有縫隙產生。所以,如果某個對象被激發了后,你應該在下一次調用MsgWaitForMultipleObjects()前把handles數組重新整理、緊壓,而不是把該位置設置為NULL了事。

3.如果有另一個線程更改了對象數組,而那是你正等待的,那么你需要一種方法,強迫MsgWaitForMultipleObjects()返回,并重新開始,以包含這個新的 handle