本系列意在記錄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