用戶模式的線程同步機制效率高,如果需要考慮線程同步問題,應該首先考慮用戶模式的線程同步方法。但是,用戶模式的線程同步有限制,對于多個進程之間的線程同步,用戶模式的線程同步方法無能為力。這時,只能考慮使用內核模式。
Windows提供了許多內核對象來實現線程的同步。對于線程同步而言,這些內核對象有兩個非常重要的狀態:“已通知”狀態,“未通知”狀態(也有翻譯為:受信狀態,未受信狀態)。Windows提供了幾種內核對象可以處于已通知狀態和未通知狀態:進程、線程、作業、文件、控制臺輸入/輸出/錯誤流、事件、等待定時器、信號量、互斥對象。
與事件EVENT的配合使用,能夠解決很多同步問題,也可以在數據達到某個狀態時啟動另一個線程的執行,如報警。
EVENT 的幾個函數:
1、CreateEvent和OpenEvent
HANDLE WINAPI CreateEvent(__in LPSECURITY_ATTRIBUTES lpEventAttributes, //表示安全控制,一般直接傳入NULL,表示不能被子進程繼承__in BOOL bManualReset, //參數確定事件是手動置位還是自動置位,傳入TRUE表示手動置位,傳入FALSE表示自動置位。__in BOOL bInitialState, //Event的初始狀態, TRUE為觸發,FALSE未觸發__in LPCTSTR lpName //Event object的名字,NULL表示沒名字(without a name)
);
要是CreateEvent創建的事件沒名字 這個函數就沒啥用了,不多做介紹,可查看msn。
HANDLE WINAPI OpenEvent( //獲得已經存在的Event的事件句柄__in DWORD dwDesiredAccess,__in BOOL bInheritHandle,__in LPCTSTR lpName //要打開的事件名字
);
2、SetEvent,觸發事件
BOOL SetEvent(HANDLE hEvent);
3、ResetEvent,使事件狀態設為未觸發,如在創建事件時第二個參數為TRUE手動設置,則需要該函數去恢復事件為未觸發狀態。
BOOL SetEvent(HANDLE hEvent);
4、PulseEvent, 如在創建事件時第二個參數為TRUE手動設置,其功能相當于SetEvent()后立即調用ResetEvent(),最好別用
BOOL PulseEvent(HANDLE hEvent)
5、CloseHandle(),關閉該句柄。
事件是內核對象,事件分為手動置位事件和自動置位事件。事件Event內部它包含一個使用計數(所有內核對象都有),一個布爾值表示是手動置位事件還是自動置位事件,另一個布爾值用來表示事件有無觸發。事件可以由SetEvent()來觸發,由ResetEvent()來設成未觸發。還可以由PulseEvent()來發出一個事件脈沖。
WaitForSingleObject()
在多線程下面,有時候我們會希望等待某一線程完成了再繼續做其他事情,要實現這個目的,可以使用Windows API函數WaitForSingleObject,或者WaitForMultipleObjects。這兩個函數都會等待Object被標為有信號(signaled)時才返回的。
那么,什么是信號呢?
簡單來說,Windows下創建的Object都會被賦予一個狀態量。如果Object被激活了,或者正在使用,那么該Object就是無信號,也就是不可用;另一方面,如果Object可用了,那么它就恢復有信號了。
這兩個函數的優點是它們在等待的過程中會進入一個非常高效沉睡狀態,只占用極少的CPU時間片。(這兩個函數都是在內核狀態下等待內核對象,不切換到用戶模式下,因而效率很高)
1、格式
DWORD WaitForSingleObject( HANDLE hHandle, DWORDdwMilliseconds);
有兩個參數,分別是THandle和Timeout(毫秒單位)。
如果想要等待一條線程,那么你需要指定線程的Handle,以及相應的Timeout時間。當然,如果你想無限等待下去,Timeout參數可以指定系統常量INFINITE。
WaitForSingleObject函數用來檢測hHandle事件的信號狀態,當函數的執行時間超過dwMilliseconds就返回,但如果參數dwMilliseconds為INFINITE時函數將直到相應時間事件變成有信號狀態才返回,否則就一直等待下去,直到WaitForSingleObject有返回值才執行后面的代碼。此外,當dwMilliseconds設置為特殊值0時,測試hHandle核心對象是否被激發,函數立即返回。
2. 使用對象
它可以等待如下幾種類型的對象:
Event(事件),Mutex(互斥量),Semaphore(信號量),Process(進程),Thread(線程),Change notification(變更通知),Console input(控制臺輸入),Job(可以被理解為進程的容器),Memory resource notification(內存資源通知),Waitable timer(等待定時器)
3. 返回類型
WAIT_ABANDONED:當hHandle為mutex時,如果擁有mutex的線程在結束時沒有釋放核心對象會引發此返回值。
WAIT_OBJECT_0:核心對象已被激活WAIT_TIMEOUT:等待超時WAIT_FAILED:出現錯誤,可通過GetLastError得到錯誤代碼
4.示例:
#include <windows.h>
#include <stdio.h>
#include <iostream.h> //聲明函數 創建線程
DWORD WINAPI FunProc( LPVOID lpParameter); void main()
{ HANDLE hThread; hThread=CreateThread(NULL,0,FunProc,NULL,0,NULL); DWORD dwRet=WaitForSingleObject(hThread, 1); if(dwRet==WAIT_OBJECT_0) { printf("創建的線程執行結束\n"); } if(dwRet==WAIT_TIMEOUT) { printf("等待超時\n"); } if(dwRet==WAIT_ABANDONED) { printf("Abandoned\n"); } CloseHandle(hThread);
} DWORD WINAPI FunProc( LPVOID lpParameter )
{ int i=1; for(; i<1000; i++) { printf("%d ", i); if(! (i%10)) printf("\n"); } return 0;
}
注意:不可以在WaitForSingleObject()之前執行CloseHandle()否則會導致程序出錯!!
官方文檔解釋:
如果在wait操作仍處于暫掛狀態時關閉此句柄,則函數的行為將不明確。
WaitForMultipleObjecct()
WaitForMultipleObjects是Windows中的一個功能非常強大的函數,幾乎可以等待Windows中的所有的內核對象
函數原型為:
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 );
參數解析:
DWORD
就是Double Word
, 每個word為2個字節的長度,DWORD
雙字即為4個字節,每個字節是8位。nCount
指定列表中的句柄數量 最大值為MAXIMUM_WAIT_OBJECTS(64)
*lpHandles
句柄數組的指針。lpHandles
為指定對象句柄組合中的第一個元素 HANDLE類型可以為(Event
,Mutex
,Process
,Thread
,Semaphore
)數組bWaitAll
等待的類型,如果為TRUE
,表示除非對象都發出信號,否則就一直等待下去;如果FALSE
,表示任何對象發出信號即可dwMilliseconds
指定要等候的毫秒數。如設為零,表示立即返回。如指定常數INFINITE
,則可根據實際情況無限等待下去
函數的返回值有:
WAIT_ABANDONED_0
:所有對象都發出消息,而且其中有一個或多個屬于互斥體(一旦擁有它們的進程中止,就會發出信號)WAIT_TIMEOUT
:對象保持未發信號的狀態,但規定的等待超時時間已經超過WAIT_OBJECT_0
:所有對象都發出信號WAIT_IO_COMPLETION
:(僅適用于WaitForMultipleObjectsEx
)由于一個I/O完成操作已作好準備執行,所以造成了函數的返回- 返回
WAIT_FAILED
則表示函數執行失敗,會設置GetLastError
如bWaitAll
為FALSE
,那么返回結果相似,只是可能還會返回相對于WAIT_ABANDONED_0
或WAIT_OBJECT_0
的一個正偏移量,指出哪個對象是被拋棄還是發出信號。
WAIT_OBJECT_0
是微軟定義的一個宏,你就把它看成一個數字就可以了。
例如,WAIT_OBJECT_0
+ 5的返回結果意味著列表中的第5個對象發出了信號
如果程序中的nObjectWait
是WAIT_OBJECT_0
+ 5
int nIndex
= nObjectWait
- WAIT_OBJECT_0
;就是說nIndex
=5
也就表示第5
個對象發出了信號
示例:
當 bWaitAll參數為FALSE可以等待其中之一的事件
HANDLE m_hEvent[2]; //兩事件 m_hEvent[0]=CreateEvent(NULL, FALSE, FALSE, NULL);
m_hEvent[1]=CreateEvent(NULL, FALSE, FALSE, NULL);
CreateThread(NULL, 0, MyThreadProc, this, 0, NULL);
DWORD WINAPI MyThreadProc(LPVOID lpParam)
{
while(TRUE) { //每次等500毫秒 int nIndex = WaitForMultipleObjects(2, pThis->m_hEvent, FALSE,500); if (nIndex == WAIT_OBJECT_0 + 1) { //第二個事件發生 //ExitThread(0); //break;
} else if (nIndex == WAIT_OBJECT_0) //第一個事件發生
{ //第一個事件 }
else if (nIndex == WAIT_TIMEOUT) //超時500毫秒
{ //超時可作定時用
}
} OutputDebugString("線程結束. /n"); return 0L;}
當要處理第一個事件時,你只需執行SetEvent(m_hEvent[0]); 即可進入第一個事件的位置
當要執行第二個事件時執行SetEvent(m_hEvent[1]);
當 bWaitAll參數為TRUE等待所有的事件
DWORD WINAPI MyThreadProc(LPVOID lpParam)
{ while(TRUE) { //每次等500毫秒
int nIndex = WaitForMultipleObjects(2, pThis->m_hEvent, TRUE,500); if (WAIT_OBJECT_0 + 1<= nIndex <= WAIT_OBJECT_0) //所有事件發生 { //所有的信號量都有效時(事件都發生)其中之一無效。 }