本系列意在記錄Windwos線程的相關知識點,包括線程基礎、線程調度、線程同步、TLS、線程池等。
用內核對象進行線程同步
內核對象:Windows操作系統使用內核對象來管理進程、線程、文件等諸多種類的大量資源。內核對象的創建通常是通過Windows API,比如CreateThread將創建一個線程內核對象,並返回一個內核對象句柄。內核對象實際上是一小塊內存,其中包括了引用計數、安全性描述等信息,操作系統通過這一小段內存來管理對應的內核資源。內核對象的實際內存地址並非句柄所展示的,它們在進程內的內核對象句柄表中有映射。
在前幾篇中,介紹了在用戶模式下的線程同步機制:InterLocked系列、關鍵段、Slim讀寫鎖。這些同步機制可以在進行線程同步的同時讓線程保持在用戶模式下。然而用戶模式下的線程同步機制有時不能滿足我們的要求。從這篇開始,將介紹使用內核對象進行線程同步。在考慮是使用用戶模式的同步機制還是使用內核對象來同步的時候,需要綜合考量,盡量使用用戶模式的線程同步機制。
內核對象普遍存在兩種狀態,要么是觸發,要么是未觸發。每種內核對象在這兩個狀態間切換過程都有其特殊的特點,比如進程內核對象在創建的時候總是處於未觸發狀態,當進程終止時,會變成觸發狀態;而且進程內核對象永遠不會從觸發態變回未觸發態。於是,如果我們的線程需要等待子進程終止時才繼續,那么可以將線程進入等待狀態,當子進程標識的進程內核對象變成觸發狀態的時候喚醒線程!我們所需要的僅僅是Windows為我們提供的等待函數。
等待函數
等待函數能夠是一個線程進入等待狀態,直到指定的內核對象被觸發為止。這些等待函數有:
DWORD WINAPI WaitForSingleObject( __in HANDLE hHandle, __in DWORD dwMilliseconds );
當線程調用WaitForSingleObject的時候,第一個參數hObject用來標識內核對象,第二個參數是個超時時間(傳入INFINITE表示永遠等待直到觸發)。
DWORD WINAPI WaitForMultipleObjects( __in DWORD nCount, __in const HANDLE *lpHandles, __in BOOL bWaitAll, __in DWORD dwMilliseconds );
WaitForMultipleObjects允許調用線程檢查多個內核對象的觸發狀態。
使用等待函數有時是會改變內核對象的狀態的。比如:線程正在等待一個自動重置事件對象,當事件對象被觸發的時候,函數會檢測到這一情況並返回調用線程,但是在返回之前,他會使事件變為非觸發狀態。等待函數在不同的內核對象上調用並返回所引起的這樣類似的“副作用”是不同的。在對內核對象展開介紹后,將看到這一點。
如果多個線程在同時等待同一個內核對象,那么任何一個線程都有可能被喚醒。我們不應該做出類似“誰先等待誰就先喚醒”這樣的假設。
接下來,將分幾篇的內容分別介紹那些與線程同步有關的內核對象。
事件內核對象
事件內核對象分為手動重置和自動重置,區別在於當對象從未觸發狀態變成觸發狀態后,會不會自動重置回未觸發狀態。與其他內核對象相同,事件內核對象也包括引用計數。下面的函數CreateEvent用以創建事件內核對象:
HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPTSTR lpName );
lpEventAttributes:會被忽略,必須為NULL(這里是MSDN的說法,在Windows Via C/C++中,暗示了這個參數與內核對象的安全屬性和共享特性有關。不過通常都會傳入NULL,因為不太會考慮讓一個事件對象變為可繼承)
bManualReset:手動重置(TRUE)還是自動重置(FALSE)
bInitialState:初始狀態為觸發(TRUE)還是非觸發(FALSE)
lpName:用於共享內核對象的機制,這里與線程同步無關,不再闡述,傳入NULL即可。
返回值:事件對象的句柄
Windows Vista還支持下面這個函數CreateEventEx來創建事件內核對象,詳情請參見MSDN:
HANDLE WINAPI CreateEventEx( __in_opt LPSECURITY_ATTRIBUTES lpEventAttributes, __in_opt LPCTSTR lpName, __in DWORD dwFlags, __in DWORD dwDesiredAccess );
我們使用下面的函數來控制事件對象的狀態:
BOOL SetEvent( HANDLE hEvent ); //將內核對象設置為觸發狀態 BOOL ResetEvent( HANDLE hEvent );//將內核對象設置為非觸發狀態 BOOL PulseEvent( HANDLE hEvent ); //觸發內核對象並立即將其重置為未觸發狀態,會喚醒正在等待的線程
如果用圖來表示他們的作用就很直觀了:
另外OpenEvent通常用於進程同步,但前提是事件內核對象必須有別名(通過給某些內核對象起別名,是一種共享內核對象的方式):
HANDLE OpenEvent( DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName );
下圖描述了事件內核對象的示例用法(同一進程內):
主線程先創建一個事件內核對象,並創建兩個線程,這兩個線程的執行需要依賴主線程的准備工作,因此調用WaitForSingleObject等待事件對象觸發。主線程完成准備工作后,調用SetEvent使對象變成觸發狀態,這時兩個子線程將被喚醒開始執行代碼。
在后面的篇章中,將繼續介紹其他可以用來線程同步的內核對象。
勞動果實,轉載請注明出處:http://www.cnblogs.com/P_Chou/archive/2012/07/03/waitobject-and-event-in-thread-sync.html