【Windows】線程漫談——線程同步之等待函數和事件內核對象


 

本系列意在記錄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 
); //觸發內核對象並立即將其重置為未觸發狀態,會喚醒正在等待的線程

如果用圖來表示他們的作用就很直觀了:

image

另外OpenEvent通常用於進程同步,但前提是事件內核對象必須有別名(通過給某些內核對象起別名,是一種共享內核對象的方式):

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

 

下圖描述了事件內核對象的示例用法(同一進程內):

image

主線程先創建一個事件內核對象,並創建兩個線程,這兩個線程的執行需要依賴主線程的准備工作,因此調用WaitForSingleObject等待事件對象觸發。主線程完成准備工作后,調用SetEvent使對象變成觸發狀態,這時兩個子線程將被喚醒開始執行代碼。

在后面的篇章中,將繼續介紹其他可以用來線程同步的內核對象。

 勞動果實,轉載請注明出處:http://www.cnblogs.com/P_Chou/archive/2012/07/03/waitobject-and-event-in-thread-sync.html


免責聲明!

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



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