事件是一個允許一個線程在某種情況發生時,喚醒另外一個線程的同步對象,或者說當應用程序必須等到發生某事才能訪問資源,應該使用事件對象。
在所有的內核對象中,事件內核對象是個最基本的對象。它們包含一個使用計數(與所有內核對象一樣),一個用於指明該事件是個自動重置的事件還是一個人工重置的事件的布爾值,另一個用於指明該事件處於已通知狀態還是未通知狀態的布爾值。
事件能夠通知一個操作已經完成。有兩種不同類型的事件對象。一種是人工重置的事件,另一種是自動重置的事件。當人工重置的事件得到通知時,等待該事件的所有線程均變爲可調度線程,需要調用ResetEvent事件才置爲未觸發狀態。當一個自動重置的事件得到通知時,等待該事件的線程中只有一個線程變爲可調度線程,同時系統將自動事件重置爲未觸發狀態。
當一個線程執行初始化操作,然後通知另一個線程執行剩餘的操作時,事件使用得最多。事件初始化爲未通知狀態,然後,當該線程完成它的初始化操作後,它就將事件設置爲已通知狀態。這時,一直在等待該事件的另一個線程發現該事件已經得到通知,因此它就變成可調度線程。這第二個線程知道第一個線程已經完成了它的操作。
1、 以SDK方式使用事件同步對象
下面是CreateEvent函數,用於創建事件內核對象:
HANDLE CreateEvent(
PSECURITY_ATTRIBUTES psa,
BOOL fManualReset,
BOOL fInitialState,
PCTSTR pszName);
psa參數,用於創建內核對象的函數幾乎都有一個指向SECURITY_ATTRIBUTES 結構的指針作爲其參數,大多數應用程序只是爲該參數傳遞NULL ,這樣就可以創建帶有默認安全性的內核對象。默認安全性意味着對象的管理小組的任何成員和對象的創建者都擁有對該對象的全部訪問權,而其他所有人均無權訪問該對象。但是,可以指定一個SECURITY_ATTRIBUTES結構,對它進行初始化,併爲該參數傳遞該結構的地址。SECURITY_ATTRIBUTES結構類似下面的樣子:
typedef struct _SECURITY_ATTRIBUTES
{
DWORD nLength,
LPVOID lpSecurityDescriptor;
BOOL bInherttHandle;
} SECURITY_ATTRIBUTES;
儘管該結構稱爲SECURITY_ATTRIBUTES,但是它包含的與安全性有關的成員實際上只有一個,即lpSecurityDescriptor。如果你想要限制人們對你創建的內核對象的訪問,必須創建一個安全性描述符,然後像下面這樣對SECURITY_ATTRIBUTES結構進行初始化:
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa); //Used for versioning
sa.lpSecuntyDescriptor = pSD, //Address of an initialized SD
sa.bInheritHandle = FALSE; //Discussed later
HANDLE hFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE,
&sa, PAGE_REAOWRITE, 0, 1024, "MyFileMapping");
fManualReset參數是個布爾值,它能夠告訴系統是創建一個人工重置的事件(TRUE)還是創建一個自動重置的事件(FALSE)。
fInitialState參數用於指明該事件是要初始化爲已通知狀態(TRUE)還是未通知狀態(FALSE)。當系統創建事件對象後,CreateEvent就將與進程相關的句柄返回給事件對象。其他進程中的線程可以獲得對該對象的訪問權,方法是使用在pszName參數中傳遞的相同值。
與所有情況中一樣,當不再需要事件內核對象時,應該調用CloseHandle函數。
一旦事件已經創建,就可以直接控制它的狀態。當調用SetEvent時,可以將事件改爲已通知狀態:
BOOL SetEvent(HANDLE hEvent);
當調用ResetEvent函數時,可以將該事件改爲未通知狀態:
BOOL ResetEvent(HANDLE hEvent);
事件的使用示例如下:
HANDLE g_hEvent;
int WINAPI WinMain(...)
{
g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
HANDLE hThread[3];
DWORD dwThreadID;
hThread[0] = _beginthreadex(NULL, 0, ThreadProc1, NULL, 0, &dwThreadID);
hThread[1] = _beginthreadex(NULL, 0, ThreadProc2, NULL, 0, &dwThreadID);
hThread[2] = _beginthreadex(NULL, 0, ThreadProc3, NULL, 0, &dwThreadID);
... //主線程要做的初始化工作
SetEvent(g_hEvent);
}
DWORD WINAPI ThreadProc1(PVOID pvParam)
{
WaitForSingleObject(g_hEvent, INFINITE);
...
return(0);
}
DWORD WINAPI ThreadProc2(PVOID pvParam)
{
WaitForSingleObject(g_hEvent, INFINITE);
...
return(0);
}
DWORD WINAPI ThreadProc3(PVOID pvParam)
{
WaitForSingleObject(g_hEvent, INFINITE);
...
return(0);
}
當這個進程啓動時,它創建一個人工重置的未通知狀態的事件,並且將句柄保存在一個全局變量中。這使得該進程中的其他線程能夠非常容易地訪問同一個事件對象。現在3個線程已經產生,這些線程要等待主線程的初始化工作完成。這3個線程函數的代碼的開始部分都相同,每個函數都調用WaitForSingleObject,這將使線程暫停運行,直到主線程主線程調用SetEvent爲止。
一旦主線程將數據準備好,它就調用SetEvent,給事件發出通知信號。這時,系統就使所有這3個輔助線程進入可調度狀態,它們都獲得了CPU時間。注意,這3個線程都以只讀方式訪問主線程初始化好的內存。這就是所有3個線程能夠同時運行的唯一原因。
如果你使用自動重置的事件而不是人工重置的事件,那麼應用程序的行爲特性就有很大的差別。當主線程調用SetEvent之後,系統只允許一個輔助線程變成可調度狀態。同樣,也無法保證系統將使哪個線程變爲可調度狀態。其餘兩個輔助線程將繼續等待。
讓我們重新編寫線程的函數,使得每個函數在返回前調用SetEvent函數(就像WinMain函數所做的那樣)。這些線程函數現在變成下面的形式:
DWORD WINAPI ThreadProc1(PVOID pvParam)
{
WaitForSingleObject(g_hEvent, INFINITE);
...
SetEvent(g_hEvent);
return(0);
}
DWORD WINAPI ThreadProc2(PVOID pvParam)
{
WaitForSingleObject(g_hEvent, INFINITE);
...
SetEvent(g_hEvent);
return(0);
}
DWORD WINAPI ThreadProc3(PVOID pvParam)
{
WaitForSingleObject(g_hEvent, INFINITE);
...
SetEvent(g_hEvent);
return(0);
}
當線程完成它對數據的專門傳遞時,它就調用SetEvent函數,該函數允許系統使得兩個正在等待的線程中的一個成爲可調度線程。同樣,我們不知道系統將選擇哪個線程作爲可調度線程,但是該線程將進行它自己的對內存塊的專門傳遞。當該線程完成操作時,它也將調用SetEvent函數,使第三個即最後一個線程進行它自己的對內存塊的傳遞。注意,當使用自動重置事件時,如果每個輔助線程均以讀/寫方式訪問內存塊,那麼就不會產生任何問題,這些線程將不再被要求將數據視爲只讀數據。這個例子清楚地展示出使用人工重置事件與自動重置事件之間的差別。
2、 使用MFC的CEvent類
CEvent 類的各成員函數的原型和參數說明如下:
1)CEvent(BOOL bInitiallyOwn=FALSE,
BOOL bManualReset=FALSE,
LPCTSTR lpszName=NULL,
LPSECURITY_ATTRIBUTES lpsaAttribute=NULL);
bInitiallyOwn:指定事件對象初始化狀態,TRUE爲有信號,FALSE爲無信號;
bManualReset:指定要創建的事件是屬於人工事件還是自動事件。TRUE爲人工事件,FALSE爲自動事件;
後兩個參數一般設爲NULL,在此不作過多說明。
2)BOOL CEvent::SetEvent();
將 CEvent 類對象的狀態設置爲有信號狀態。如果事件是人工事件,則 CEvent 類對象保持爲有信號狀態,直到調用成員函數ResetEvent()將其重新設爲無信號狀態時爲止。如果CEvent 類對象爲自動事件,則在SetEvent()將事件設置爲有信號狀態後,CEvent 類對象由系統自動重置爲無信號狀態。
如果該函數執行成功,則返回非零值,否則返回零。
3)BOOL CEvent::ResetEvent();
該函數將事件的狀態設置爲無信號狀態,並保持該狀態直至SetEvent()被調用時爲止。由於自動事件是由系統自動重置,故自動事件不需要調用該函數。如果該函數執行成功,返回非零值,否則返回零。
一般通過調用WaitForSingleObject函數來監視事件狀態。