WaitForMultipleObject与MsgWaitForMultipleObjects用法
用戶模式的線程同步機制效率高,如果需要考慮線程同步問題,應該首先考慮用戶模式的線程同步方法。但是,用戶模式的線程同步有限制,對于多個進程之間的線程同步,用戶模式的線程同步方法無能為力。這時,只能考慮使用內核模式。
用戶模式與內核模式線程同步機制比較:
| ? | 用戶模式 | 內核模式 |
| 優點 | 線程同步機制速度快 ? | 支持多個進程之間的線程同步 防止死鎖 |
| 缺點 | 容易陷入死鎖狀態 多個進程之間的線程同步會出現問題。(比如競爭資源、死鎖) | 線程同步機制速度慢 線程必須從用戶模式轉為內核模式。這個轉換需要很大的代價:往返一次需要占用x 8 6平臺上的大約1 0 0 0個C P U周期 |
?
Windows提供了許多內核對象來實現線程的同步。對于線程同步而言,這些內核對象有兩個非常重要的狀態:“已通知”狀態,“未通知”狀態(也有翻譯為:受信狀態,未受信狀態)。Windows提供了幾種內核對象可以處于已通知狀態和未通知狀態:進程、線程、作業、文件、控制臺輸入/輸出/錯誤流、事件、等待定時器、信號量、互斥對象。
你可以通知一個內核對象,使之處于“已通知狀態”,然后讓其他等待在該內核對象上的線程繼續執行。你可以使用Windows提供的API函數,等待函數來等待某一個或某些內核對象變為已通知狀態。
?
一、WaitForSingleObject、WaitForMulitpleObjects
函數功能: 等待一個內核對象變為已通知狀態
可以使用WaitForSingleObject函數來等待一個內核對象變為已通知狀態:
DWORD WaitForSingleObject(
HANDLE hObject, //指明一個內核對象的句柄
DWORD dwMilliseconds); //等待時間
該函數需要傳遞一個內核對象句柄,該句柄標識一個內核對象,如果該內核對象處于未通知狀態,則該函數導致線程進入阻塞狀態;如果該內核對象處于已通知狀態,則該函數立即返回WAIT_OBJECT_0。第二個參數指明了需要等待的時間(毫秒),可以傳遞INFINITE指明要無限期等待下去,如果第二個參數為0,那么函數就測試同步對象的狀態并立即返回。如果等待超時,該函數返回WAIT_TIMEOUT。如果該函數失敗,返回WAIT_FAILED。可以通過下面的代碼來判斷:
?
DWORD dw = WaitForSingleObject(hProcess, 5000); //等待一個進程結束
switch (dw)
{
case WAIT_OBJECT_0:
// hProcess所代表的進程在5秒內結束
break;
?
case WAIT_TIMEOUT:
// 等待時間超過5秒
break;
?
case WAIT_FAILED:
// 函數調用失敗,比如傳遞了一個無效的句柄
break;
}
還可以使用WaitForMulitpleObjects函數來等待多個內核對象變為已通知狀態:
DWORD WaitForMultipleObjects(
DWORD dwCount, //等待的內核對象個數
CONST HANDLE* phObjects, //一個存放被等待的內核對象句柄的數組
BOOL bWaitAll, //是否等到所有內核對象為已通知狀態后才返回
DWORD dwMilliseconds); //等待時間
該函數的第一個參數指明等待的內核對象的個數,可以是0到MAXIMUM_WAIT_OBJECTS(64)中的一個值。phObjects參數是一個存放等待的內核對象句柄的數組。bWaitAll參數如果為TRUE,則只有當等待的所有內核對象為已通知狀態時函數才返回,如果為FALSE,則只要一個內核對象為已通知狀態,則該函數返回。第四個參數和WaitForSingleObject中的dwMilliseconds參數類似。
該函數失敗,返回WAIT_FAILED;如果超時,返回WAIT_TIMEOUT;如果bWaitAll參數為TRUE,函數成功則返回WAIT_OBJECT_0,如果bWaitAll為FALSE,函數成功則返回值指明是哪個內核對象收到通知。
?
可以如下使用該函數:
HANDLE h[3]; //句柄數組
//三個進程句柄
h[0] = hProcess1;
h[1] = hProcess2;
h[2] = hProcess3;
DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000); //等待3個進程結束
switch (dw)
{
case WAIT_FAILED:
// 函數呼叫失敗
break;
?
case WAIT_TIMEOUT:
// 超時
break;
?
case WAIT_OBJECT_0 + 0:
// h[0](hProcess1)所代表的進程結束
break;
?
case WAIT_OBJECT_0 + 1:
// h[1](hProcess2)所代表的進程結束
break;
?
case WAIT_OBJECT_0 + 2:
// h[2](hProcess3)所代表的進程結束
break;
}
?
你也可以同時通知一個內核對象,同時等待另一個內核對象,這兩個操作以原子的方式進行:
DWORD SignalObjectAndWait(
HANDLE hObjectToSignal, //通知的內核對象
HANDLE hObjectToWaitOn, //等待的內核對象
DWORD dwMilliseconds, //等待的時間
BOOL bAlertable); //與IO完成端口有關的參數,暫不討論
該函數在內部使得hObjectToSignal參數所指明的內核對象變成已通知狀態,同時等待hObjectToWaitOn參數所代表的內核對象。dwMilliseconds參數的用法與WaitForSingleObject函數類似。
該函數返回如下:WAIT_OBJECT_0,WAIT_TIMEOUT,WAIT_FAILED,WAIT_IO_COMPLETION。
等你需要通知一個互斥內核對象并等待一個事件內核對象的時候,可以這么寫:
ReleaseMutex(hMutex);
WaitForSingleObject(hEvent, INFINITE);
可是,這樣的代碼不是以原子的方式來操縱這兩個內核對象。因此,可以更改如下:
SignalObjectAndWait(hMutex, hEvent, INFINITE, FALSE);
?
二、MsgWaitForMultipleObjects
函數功能:阻塞時仍可以響應消息
MsgWaitForMultipleObjects()函數類似WaitForMultipleObjects(),但它會在“對象被激發”或“消息到達隊列”時被喚醒而返回。MsgWaitForMultipleObjects()多接收一個參數,允許指定哪些消息是觀察對象。
?
DWORD MsgWaitForMultipleObjects(
? DWORD nCount,????????? // 表示pHandles所指的handles數組的元素個數,最大容量是MAXIMUM_WAIT_OBJECTS
?
? LPHANDLE pHandles,???? // 指向一個由對象handles組成的數組,這些handles的類型不需要相同
? BOOL fWaitAll,???????? // 是否等待所有的handles被激發才返回
? DWORD dwMilliseconds,? // 超時時間
? DWORD dwWakeMask?????? // 欲觀察的用戶輸入消息類型
);
?
參數
dwWakeMask
欲觀察的用戶輸入消息類型: Value Meaning
QS_ALLEVENTS An input, WM_TIMER, WM_PAINT, WM_HOTKEY, or posted message is in the queue.
QS_ALLINPUT Any message is in the queue.
QS_ALLPOSTMESSAGE A posted message (other than those listed here) is in the queue.?
QS_HOTKEY A WM_HOTKEY message is in the queue.
QS_INPUT An input message is in the queue.
QS_KEY A WM_KEYUP, WM_KEYDOWN, WM_SYSKEYUP, or WM_SYSKEYDOWN message is in the queue.
QS_MOUSE A WM_MOUSEMOVE message or mouse-button message (WM_LBUTTONUP, WM_RBUTTONDOWN, and so on).
QS_MOUSEBUTTON A mouse-button message (WM_LBUTTONUP, WM_RBUTTONDOWN, and so on).
QS_MOUSEMOVE A WM_MOUSEMOVE message is in the queue.
QS_PAINT A WM_PAINT message is in the queue.
QS_POSTMESSAGE A posted message (other than those just listed) is in the queue.
QS_SENDMESSAGE A message sent by another thread or application is in the queue.
QS_TIMER A WM_TIMER message is in the queue
?
返回值
WAIT_TIMEOUT :因時間終了而返回
WAIT_OBJECT_0 :當bWaitAll是TRUE
WAIT_OBJECT_0 to (WAIT_OBJECT_0 + nCount – 1) :bWaitAll是FALSE,將返回值減去WAIT_OBJECT_0,就表示數組中哪一個handle被激發了
WAIT_ABANDONED_0 to (WAIT_ABANDONED_0 + nCount – 1) :等待的對象中有任何mutexes
WAIT_FAILED :函數失敗時返回該值,可以使用GetLastError()找出失敗的原因
WAIT_OBJECT_0 + nCount :消息到達隊列
?
MsgWaitForMultipleObjects()的正確使用方式是改寫主消息循環,使得激發狀態的handles得以像消息一樣被對待。通常程序中只會有一個地方調用MsgWaitForMultipleObjects(),而這個調用存在于消息循環中。
注意:
1. 在收到WM_QUIT之后,Windows仍然會傳送消息給你,如果要在收到WM_QUIT之后等待所有線程結束,必須繼續處理你的消息,否則窗口會變得反應遲鈍,而且沒有重繪能力。
2.MsgWaitForMultipleObjects()不允許handles數組中有縫隙產生。所以當某個handle被激發了時,應該在下一次調用MsgWaitForMultipleObjects之前先把handles數組做個整理、緊壓,不要只是把數組中的handle設為NULL
3.如果有另一個線程改變了對象數組,而那是你正在等待的,那么需要一種方法,可以強迫MsgWaitForMultipleObjects返回,并重新開始,以包含新的handle
?
三、MsgWaitForMultipleObjectsEx
函數功能:阻塞時仍可以響應消息
函數原型
DWORD MsgWaitForMultipleObjectsEx(
DWORD nCount, // 句柄數組中句柄數目
LPHANDLE pHandles, // 指向句柄數組的指針
DWORD dwMilliseconds, // 以毫秒計的超時值
DWORD dwWakeMask, // 要等待的輸入事件類型
DWORD dwFlags // 等待標志
);
?
參數
nCount,指定pHandles指向的數組中的對象句柄數目。最大對象數目是MAXIMUM_WAIT_OBJECTS-1
pHandles ,指向一個對象句柄數組。要得到可以使用的對象句柄類型清單,請查看備注部分。數組中可以包含多種對象類型。 Windows NT: 數組中句柄必須擁有SYNCHRONIZE訪問權。要得到更多相關信息,請查閱MSDN中Standard Access Rights。
dwMilliseconds ,指定以毫秒計的超時值。即使參數dwWakeMask與dwFlags中指定的條件未滿足,超時后函數仍然返回。如果dwMilliseconds值為0,函數測試指定的對象狀態并立即返回。如果dwMilliseconds值為INFINITE,函數超時周期為無窮大。
dwWakeMask ,指定被加到對象句柄數組中的輸入事件對象句柄的對象類型。這個參數可以是下面列出值的任意組合:
值含義
QS_ALLEVENTS
?WM_TIMER, WM_PAINT, WM_HOTKEY輸入消息或登記消息(posted message)在消息隊列中
QS_ALLINPUT
?任何消息在消息隊列中
QS_ALLPOSTMESSAGE
?登記消息(在此處列出的除外)在消息隊列中
QS_HOTKEY
?WM_HOTKEY消息在消息隊列中
QS_INPUT
?輸入消息在消息隊列中
?
QS_KEY
?WM_KEYUP,WM_KEYDOWN,WM_SYSKEYUP或WM_SYSKEYDOWN消息在消息隊列中
QS_MOUSE
?WM_MOUSEMOVE消息或鼠標點擊消息(WM_LBUTTONUP,WM_RBUTTONDOWN等)在消息隊列中
QS_MOUSEBUTTON
?鼠標點擊消息(WM_LBUTTONUP,WM_RBUTTONDOWN等)在消息隊列中
QS_MOUSEMOVE
?WM_MOUSEMOVE消息在消息隊列中
QS_PAINT
?WM_PAINT消息在消息隊列中
QS_POSTMESSAGE
?登記消息(在此處列出的除外)在消息隊列中
QS_SENDMESSAGE
?由另一個線程或應用發送的消息在消息隊列中?
QS_TIMER
?WM_TIMER消息在消息隊列中
?
dwFlags ,指定等待類型。這個參數可以是下面列出值的任意組合:
值含義
0
當對象中任意一個變為有信號狀態則函數返回。返回值指出是哪個對象狀態的改變導致函數返回。
MWMO_WAITALL
只有當pHandles數組中所有對象有信號時函數返回
MWMO_ALERTABLE
調用QueueUserAPC加入一個APC將導致函數返回
MWMO_INPUTAVAILABLE
只適用于Windows 98, Windows NT 5.0及其以后版本: 消息隊列中存在輸入函數將返回,甚至于輸入已經被另一個函數檢測過了,如PeekMessage函數
?
返回值
假如函數成功,返回值表明引起函數返回的事件。成功的函數值是下面中的一個:
值含義
WAIT_OBJECT_0 到(WAIT_OBJECT_0 + nCount - 1)
假如MWMO_WAITALL標志置位,返回值指明所有指定的對象處于有信號狀態。返回值減去WAIT_OBJECT_0就是pHandles數組中引起函數返回的對象的索引
WAIT_OBJECT_0 + nCount
有新的在dwWakeMask參數中指定的輸入類型存在于輸入隊列中。函數如:PeekMessage,GetMessage,GetQueueStatus與WaitMessage將隊列中的消息標記為舊的。因此,當你在這些函數之后調用MsgWaitForMultipleObjectsEx,函數將不會返回,除非有新的被指定的輸入到達。當一個需要該線程活動的系統事件發生時也將返回該值,例如前臺活動。因此即使沒有相應的輸入發生或dwWaitMask置0,MsgWaitForMultipleObjectsEx也可以返回。如果發生這種情況,那么在再次調用MsgWaitForMultipleObjectsEx之前要調用PeekMessage或GetMessage處理系統事件。
?
WAIT_ABANDONED_0 到(WAIT_ABANDONED_0 + nCount - 1)
假如MWMO_WAITALL標志置位,返回值指明所有指定的對象處于有信號狀態并且至少其中的一個是一個被舍棄的(abandoned)互斥對象。另外,返回值減去WAIT_ABANDONED_0即是pHandles數組中引起函數返回的被舍棄的互斥對象的索引
?
WAIT_IO_COMPLETION
等待被一加入隊列中的用戶模式異步過程調用(user-mode asynchronous procedure call (APC))所終止
?
WAIT_TIMEOUT
超時,但dwFlags與dwWakeMask參數中條件未滿足
?
假如函數調用失敗,返回值是0xFFFFFFFF。若想獲得更多的錯誤信息,請調用GetLastError函數。
?
備注
MsgWaitForMultipleObjectsEx函數檢測是否dwWakeMask與dwFlags參數中指定的條件滿足。假如條件未滿足,調用線程進入高效的等待狀態。線程在等待條件之一滿足或超時時只用很少的處理器時間。
返回前,等待函數會修改某些異步對象的狀態。修改只會針對那些置信號狀態后會導致函數返回的對象,例如系統將信號對象(semaphore)的引用計數減一。當dwFlags為零并且多個對象處于信號狀態時,函數選擇對象中的一個來確保等待;未被選中的對象的狀態不受影響。
?
MsgWaitForMultipleObjectsEx函數可以在pHandles數組中指定下列的對象類型:
改變通知(Change notification)
控制臺輸入
事件
作業(job)
互斥
進程
信號
線程
等待計時器
要獲取更多信息,請參閱Synchronization Objects
?
QS_ALLPOSTMESSAGE與QS_POSTMESSAGE標志被消除時是有區別的。QS_POSTMESSAGE在你調用GetMessage或PeekMessage時被消除,而不管你是否正在過濾消息。QS_ALLPOSTMESSAGE在你調用不過濾消息(wMsgFilterMin與wMsgFilterMax皆為零)的GetMessage或PeekMessage時被消除。這在你調用PeekMessage多次以獲得不同區域的消息時會很有用。
?
Windows CE:Windows CE不支持QS_HOTKEY賦予dwWakeMask參數,也不支持MWMO_WAITALL和MWMO_ALERTABLE標志賦予dwFlags參數。
?
MsgWaitForMultipleObjectsEx復制句柄表,將消息隊列事件加入其中,然后調用WaitForMultipleObjects
?
示例代碼段
?
//一段代碼,等待線程與事件對象及消息,超時值為2000毫秒
?
CWinThread* pThread = AfxBeginThread((AFX_THREADPROC)YourThreadFun, NULL);
?
HANDLE hThreadAndEvent[ 2 ];
?
hThreadAndEvent[ 0 ] = pThread->m_hThread;
?
hThreadAndEvent[ 1 ] = ::CreateEvent( NULL, FALSE, FALSE, NULL );
?
DWORD dwReturn = ::MsgWaitForMultipleObjectsEx(2,
hThreadAndEvent,
2000,//2秒醒來一次
QS_ALLEVENTS,
MWMO_INPUTAVAILABLE
);
?
if ( dwReturn==WAIT_OBJECT_0 )
{
?
//線程對象通知
?
}
if ( dwReturn==WAIT_OBJECT_0+1 )
{
?
//事件對象通知
?
}
if ( dwReturn == WAIT_OBJECT_0+2 )
{
?
//消息
?
}
if ( dwReturn == WAIT_TIMEOUT )
?
{
?
//超時
?
}
總結
以上是生活随笔為你收集整理的WaitForMultipleObject与MsgWaitForMultipleObjects用法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 等待一个线程的结束
- 下一篇: Sqlite-Sqlite3中的数据类型