第三章 快跑和等待
資源網絡收集 感謝原創者
轉自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之后等待所有線程的結束,你必須繼續處理你的消息,否則窗口會變得反應遲鈍,喪失重繪能力。
2.MsgWaitForMultipleObjects()不允許handles數組中有縫隙產生。所以,如果某個對象被激發了后,你應該在下一次調用MsgWaitForMultipleObjects()前把handles數組重新整理、緊壓,而不是把該位置設置為NULL了事。
3.如果有另一個線程更改了對象數組,而那是你正等待的,那么你需要一種方法,強迫MsgWaitForMultipleObjects()返回,并重新開始,以包含這個新的 handle。