本書首先介紹了一個重要的概念“成功的副作用”,這裡筆者作一下簡述。
當調用WaitForSingleObject和WaitForMultipleObject函數成功之後,該函數在返回成功的時候,系統可能會自動更改所等待的核心對象的狀態,即將其從“已通知狀態”切換為“未通知狀態”。
當一個核心對象的狀態被更改,稱之為“成功等待的副作用”。比如,一個“自動重設”的事件核心對象,當調用等待函數成功返回的時候,該事件核心對象會由已通知狀態轉變為未通知狀態。
比如此時有一個自動重設的事件核心對象hEvent,它處於未通知狀態。線程T1、T2、T3內部調用“WaitForSingleObject(hEvent, INFINITE);”,這樣當該事件核心對象變為“已通知”狀態的話,T1線程“可能”被喚醒,但是其他的線程T2和T3呢?由於在T1線程內部WaitForSingleObject函數返回成功,又將hEvent事件核心對象設定為“未通知”狀態,那麼T2和T3就不可能被喚醒。
也就是說,“成功等待的副作用”會導致多個等待在同一個核心對象上的線程只能被喚醒一個。
好,下面我們來討論“事件核心對象”。
在所有核心對象中,事件核心對象是最基本的一個核心對象。在事件核心對象內部,有以下幾個比較重要的資料:
1、有一個“引用計數”:指明被開啟的次數;
2、一個“布爾值”:指明該事件核心對象是自動重設的還是人工重設的;
3、另一個“布爾值”:指明該事件核心對象是“已通知狀態”還是“未通知狀態”。
事件核心對象可以通知一個事件已經完成。有兩種不同的類型:自動重設和人工重設。當人工重設的事件核心對象得到通知的時候,所有等待在事件核心對象上的線程都變成可調度線程。當一個自動重設的事件核心對象得到通知的時候,等待在該事件核心對象上的線程只有一個能變成可調度狀態。
要使用事件核心對象,首先調用CreateEvent函數來建立一個事件核心對象:
HANDLE CreateEvent(
PSECURITY_ATTRIBUTES psa,
BOOL bManualReset,
BOOL bInitialState,
PCTSTR pszName);
參數psa是一個SECURITY_ATTRIBUTES(安全屬性)結構的指標,一般設定為預設安全,傳遞NULL。
bManualReset參數指定了該核心對象是人工重設(傳遞TRUE)的還是自動重設(傳遞FALSE)的。
bInitialState參數指定了該核心對象起始狀態是已通知(傳遞TRUE)還是未通知狀態(FALSE)。
pszName參數為要建立的事件核心對象起一個名字,如果傳遞NULL,則建立一個“匿名”的事件核心對象。如果不傳遞NULL,且系統中已經存在該名字的事件核心對象,則不建立新的事件核心對象而是開啟這個已經存在的,返回它的控制代碼。
該函數如果成功,返回事件核心對象的控制代碼,這樣就可以操縱它了。如果失敗,返回NULL。
Windows Vista提供了另一個函數來建立事件核心對象:
HANDLE CreateEventEx(
PSECURITY_ATTRIBUTES psa,
PCTSTR pszName,
DWORD dwFlags,
DWORD dwDesiredAccess);
該函數的psa和pszName參數的意義和函數CreateEvent相同。
參數dwFlags可以有以下資料的“位或組合”:
WinBase.h中定義的位組合資料 |
描述 |
CREATE_EVENT_INITIAL_SET (0x00000002) |
如果設定了該資料,則表明事件核心對象的起始狀態為已通知狀態;否則起始狀態為未通知狀態。 |
CREATE_EVENT_MANUAL_RESET (0x00000001) |
如果設定了該資料,則表明事件核心對象是人工重設的;否則為自動重設的。 |
參數dwDesiredAccess可以讓你對該事件核心對象的訪問加一些限制,本書沒有細說,查MSDN就可以了吧。
可以開啟一個“命名”的事件核心對象:
HANDLE OpenEvent(
DWORD dwDesiredAccess,
BOOL bInherit,
PCTSTR pszName);
第一個參數指明的訪問的限制,第二個參數表示該事件核心對象的控制代碼能夠被子進程繼承,第三個參數指明了該事件核心對象的名字。該函數成功返回事件核心對象的控制代碼,失敗返回NULL。
當不需要使用這些控制代碼時,需要調用CloseHandle函數來遞減核心對象的引用計數,使得該核心對象可以被及時清除。
當一個事件核心對象被建立之後,你可以直接控制它的狀態。你可以通知它,使得它從未通知狀態轉變為已通知狀態:
BOOL SetEvent(HANDLE hEvent);
也可以重新設定它,使它從已通知狀態變為未通知狀態:
BOOL ResetEvent(HANDLE hEvent);
一個自動重設的事件核心對象,如果等待成功,由於“成功等待的副作用”機制會將該事件核心對象由已通知狀態變為未通知狀態,這個時候就沒有必要調用ResetEvent函數了。
如果是一個人工重設的事件核心對象,等待成功之後,並不會被設定為未通知狀態,而是要程式員調用ResetRvent函數來使之轉變為未通知狀態。
還有要注意的就是,一個“自動重設”的事件核心對象收到通知,轉變為已通知狀態的時候,最多隻能喚醒“一個”等待在它上的線程。一個“人工重設”的事件核心對象收到通知,轉變為已通知狀態的時候,能夠喚醒“所有”等待在它上的線程。