C++多線程同步之事件(Event)


原文鏈接:http://blog.csdn.net/olansefengye1/article/details/53291074

 

一、事件(Event)原理解析

1、線程同步Event,主要用於線程間的等待通知。 
2、內核對象中,事件內核對象是個最基本的對象。 
3、事件包含一個使用計數(與所有內核對象一樣),一個用於指明該事件是個自動重置的事件還是人工重置的事件的布爾值,另一個用於指明該事件處於已通知狀態還是未通知狀態的布爾值。 
4、事件能夠通知一個操作已經完成。 
5、有兩種不同類型的事件對象。一種是人工重置的事件,另一種是自動重置的事件。當人工重置的事件得到通知時,等待該事件的所有線程均變為可調度線程。當一個自動重置的事件得到通知時,等待該事件的線程中只有一個線程變為可調度線程。 
6、當一個線程執行初始化操作,然后通知另一個線程執行剩余的操作時,事件使用得最多。 
7、事件初始化為未通知狀態,然后,當該線程完成它的初始化操作后,它就將事件設置為已通知狀態。這時,一直在等待該事件的另一個線程發現該事件已經得到通知,因此它就變成可調度線程。

二、Win32平台源碼

1、相關頭文件及API

函數名 函數說明
CreateEvent Creates or opens a named or unnamed event object.
CreateEventEx Creates or opens a named or unnamed event object and returns a handle to the object.
OpenEvent Opens an existing named event object.
PulseEvent Sets the specified event object to the signaled state and then resets it to the nonsignaled state after releasing the appropriate number of waiting threads.
ResetEvent Sets the specified event object to the nonsignaled state.
SetEvent Sets the specified event object to the signaled state.
/*頭文件*/ #include<windows.h>
HANDLE CreateEvent( LPSECURITY_ATTRIBUTESlpEventAttributes, // 安全屬性 BOOLbManualReset, // 復位方式 BOOLbInitialState, // 初始狀態 LPCTSTRlpName // 對象名稱 );

參數: 
lpEventAttributes[In]: 一個指向SECURITY_ATTRIBUTES結構的指針,確定返回的句柄是否可被子進程繼承。如果lpEventAttributes是NULL,此句柄不能被繼承。Windows NT/2000:lpEventAttributes的結構中的成員為新的事件指定了一個安全符。如果lpEventAttributes是NULL,事件將獲得一個默認的安全符。 
bManualReset[In]: 指定將事件對象創建成手動復原還是自動復原。如果是TRUE,那么必須用ResetEvent函數來手工將事件的狀態復原到無信號狀態。如果設置為FALSE,當事件被一個等待線程釋放以后,系統將會自動將事件狀態復原為無信號狀態。 
bInitialState[In]: 指定事件對象的初始狀態。如果為TRUE,初始狀態為有信號狀態;否則為無信號狀態。 
lpName[In]:指定事件的對象的名稱,是一個以0結束的字符串指針。名稱的字符格式限定在MAX_PATH之內。名字是對大小寫敏感的。 如果lpName指定的名字,與一個存在的命名的事件對象的名稱相同,函數將請求EVENT_ALL_ACCESS來訪問存在的對象。這時候,由於bManualReset和bInitialState參數已經在創建事件的進程中設置,這兩個參數將被忽略。如果lpEventAttributes是參數不是NULL,它將確定此句柄是否可以被繼承,但是其安全描述符成員將被忽略。 如果lpName為NULL,將創建一個無名的事件對象。 如果lpName的和一個存在的信號、互斥、等待計時器、作業或者是文件映射對象名稱相同,函數將會失敗,在GetLastError函數中將返回ERROR_INVALID_HANDLE。造成這種現象的原因是這些對象共享同一個命名空間。 
返回值:如果函數調用成功,函數返回事件對象的句柄。如果對於命名的對象,在函數調用前已經被創建,函數將返回存在的事件對象的句柄,而且在GetLastError函數中返回ERROR_ALREADY_EXISTS。如果函數失敗,函數返回值為NULL,如果需要獲得詳細的錯誤信息,需要調用GetLastError。

HANDLE WINAPI CreateEventEx(
__in_opt LPSECURITY_ATTRIBUTES lpEventAttributes, __in_opt LPCTSTR lpName, __in DWORD dwFlags, __in DWORD dwDesiredAccess );

參數: 
lpEventAttributes[in, optional] :一個指向SECURITY_ATTRIBUTES結構的指針,如果該參數設為NULL,那么事件內核對象的句柄不能被子進程繼承. 
lpName[in, optional] :指向事件內核對象的名稱字符串的指針,如果該參數設為NULL,那么這個對象被創建為一個匿名事件內核對象. 
dwFlags[in] :這個參數可被設為以下一個或多個值:CREATE_ EVENT_ INITIAL_ SET 0x00000002 表示對象初始狀態為已觸發,否則為未觸發;CREATE_ EVENT_ MANUAL_RESET 0x00000001 表示這個事件對象必須用ResetEvents函數手動重置,如果不設置這個標志,系統會在內核對象被釋放后自動重置. 
dwDesiredAccess[in] :訪問權限描述標記。 
返回值:如果函數調用成功,返值是所創建或打開的事件內核對象的句柄.如果調用失敗則返回NULL。

HANDLE OpenEvent(
                DWORD dwDesiredAccess,
                BOOL bInheritHandle, LPCTSTR lpName );

參數說明: 
dwDesiredAccess [in]:指定對事件對象的請求訪問權限,如果安全描述符指定的對象不允許要求通過對調用該函數的過程,函數將返回失敗。該參數必須設置為以下值:EVENT_ALL_ACCESS 指定事件對象所有可能的權限。 
bInheritHandle [in]:指定是否返回的句柄是否繼承 。該參數必須設置為false。 
lpName[in]:指向一個以null結束的字符串,即將要打開的事件對象的名字。名稱是區分大小寫的。 
返回值:函數執行成功則返回事件對象的句柄;失敗則返回NULL,獲取錯誤信息可以使用GetLastError。

DWORD WINAPI WaitForSingleObject(
__in HANDLE hHandle, __in DWORD dwMilliseconds );

參數: 
hHandle[in]:對象句柄。可以指定一系列的對象,如Event、Job、Memory resource notification、Mutex、Process、Semaphore、Thread、Waitable timer等。 
dwMilliseconds[in]:定時時間間隔,單位為milliseconds(毫秒).如果指定一個非零值,函數處於等待狀態直到hHandle標記的對象被觸發,或者時間到了。如果dwMilliseconds為0,對象沒有被觸發信號,函數不會進入一個等待狀態,它總是立即返回。如果dwMilliseconds為INFINITE,對象被觸發信號后,函數才會返回。

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 ); 

參數: 
nCount: 句柄的數量 最大值為MAXIMUM_WAIT_OBJECTS(64) 
HANDLE: 句柄數組的指針。 HANDLE 類型可以為(Event,Mutex,Process,Thread,Semaphore )數組 。 
BOOL bWaitAll: 等待的類型,如果為TRUE 則等待所有信號量有效在往下執行,FALSE 當有其中一個信號量有效時就向下執行。 
DWORD dwMilliseconds: 超時時間 超時后向執行。 如果為WSA_INFINITE 永不超時。如果沒有信號量就會在這死等。

另外: 
一個Event被創建以后,可以用OpenEvent()**API來獲得它的Handle,用**CloseHandle() 來關閉它,用SetEvent()PulseEvent()來設置它使其有信號,用ResetEvent() 來使其無信號,用WaitForSingleObject()WaitForMultipleObjects()來等待 其變為有信號。 
PulseEvent()是一個比較有意思的使用方法,正如這個API的名字,它使一個Event 對象的狀態發生一次脈沖變化,從無信號變成有信號再變成無信號,而整個操作是原子的. 對自動復位的Event對象,它僅釋放第一個等到該事件的thread(如果有),而對於 人工復位的Event對象,它釋放所有等待的thread.

3、Win32源碼

/************************MyEvent.h******************************/ #ifndef _MY_EVENT_H #define _MY_EVENT_H #include <windows.h> class CMyEvent { public: CMyEvent() { m_hEvent = CreateEvent(NULL /*安全屬性指針*/ , false /*復位方式*/ , true /*初始化狀態*/ , NULL /*事件名稱*/ ); if(NULL == m_hEvent) { return; } } ~CMyEvent() { CloseHandle(m_hEvent); } void Lock() { WaitForSingleObject(m_hEvent,INFINITE); } void UnLock() { SetEvent(m_hEvent); } private: HANDLE m_hEvent; }; class CEventAutoLock { public: CEventAutoLock(CMyEvent* pMyEvent) : m_pMyEvent(pMyEvent) { if(NULL != m_pMyEvent) { m_pMyEvent->Lock(); } } ~CEventAutoLock() { m_pMyEvent->UnLock(); } private: CMyEvent *m_pMyEvent; }; #endif
/****************************main.cpp****************************/ #include <iostream> #include <windows.h> #include "MySemaphore.h" #include "MyMutex.h" #include "MyCriticalSection.h" #include "MyEvent.h" using namespace std; CMySemaphore g_MySemaphore; //信號量 CMyMutex g_MyMutex; //互斥量 CMyCriticalSection g_MyCriticalSection; //臨界區 CMyEvent g_MyEvent; //事件 DWORD WINAPI Fun(LPVOID lpParamter) { string strPrint((const char*)lpParamter); int iRunTime = 0; //執行100次跳出 while(++iRunTime<10) { { CEventAutoLock clock(&g_MyEvent); cout <<"["<< iRunTime <<"]:"<< strPrint.c_str()<<endl; } } return 0; } int main() { //創建五個子線程 string str1 = "A"; string str2 = "B"; string str3 = "C"; string str4 = "D"; string str5 = "E"; HANDLE hThread1 = CreateThread(NULL, 0, Fun, (void*)str1.c_str(), 0, NULL); HANDLE hThread2 = CreateThread(NULL, 0, Fun, (void*)str2.c_str(), 0, NULL); HANDLE hThread3 = CreateThread(NULL, 0, Fun, (void*)str3.c_str(), 0, NULL); HANDLE hThread4 = CreateThread(NULL, 0, Fun, (void*)str4.c_str(), 0, NULL); HANDLE hThread5 = CreateThread(NULL, 0, Fun, (void*)str5.c_str(), 0, NULL); //關閉線程 CloseHandle(hThread1); CloseHandle(hThread2); CloseHandle(hThread3); CloseHandle(hThread4); CloseHandle(hThread5); getchar(); // system("pause"); return 0; }

執行結果: 
這里寫圖片描述

Linux平台

在Linux平台下沒有專門的Event(事件)對象,可以依靠Mutex實現和Event相同的功能。Mutex的使用可參見我前面的博客。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM