深入淺出Win32多線程程序設計之線程通信 作者:宋寶華 出處:天極開發(fā)線程之間通信的兩個基本問題是互斥和同步,。 簡介 線程之間通信的兩個基本問題是互斥和同步,。 線程同步是指線程之間所具有的一種制約關系,一個線程的執(zhí)行依賴另一個線程的消息,,當它沒有得到另一個線程的消息時應等待,,直到消息到達時才被喚醒。 線程互斥是指對于共享的操作系統(tǒng)資源(指的是廣義的"資源",,而不是Windows的.res文件,,譬如全局變量就是一種共享資源),在各線程訪問時的排它性,。當有若干個線程都要使用某一共享資源時,,任何時刻最多只允許一個線程去使用,其它要使用該資源的線程必須等待,直到占用資源者釋放該資源,。 線程互斥是一種特殊的線程同步,。 實際上,互斥和同步對應著線程間通信發(fā)生的兩種情況: (1)當有多個線程訪問共享資源而不使資源被破壞時,; ?。?)當一個線程需要將某個任務已經(jīng)完成的情況通知另外一個或多個線程時。 在WIN32中,,同步機制主要有以下幾種: ?。?)事件(Event); (2)信號量(semaphore); ?。?)互斥量(mutex); (4)臨界區(qū)(Critical section),。 全局變量 因為進程中的所有線程均可以訪問所有的全局變量,,因而全局變量成為Win32多線程通信的最簡單方式。例如:
上述程序中使用全局變量和while循環(huán)查詢進行線程間同步,,實際上,,這是一種應該避免的方法,因為: ?。?)當主線程必須使自己與ThreadFunc函數(shù)的完成運行實現(xiàn)同步時,,它并沒有使自己進入睡眠狀態(tài)。由于主線程沒有進入睡眠狀態(tài),,因此操作系統(tǒng)繼續(xù)為它調(diào)度C P U時間,,這就要占用其他線程的寶貴時間周期; ?。?)當主線程的優(yōu)先級高于執(zhí)行ThreadFunc函數(shù)的線程時,,就會發(fā)生globalFlag永遠不能被賦值為true的情況。因為在這種情況下,,系統(tǒng)決不會將任何時間片分配給ThreadFunc線程,。 事件 事件(Event)是WIN32提供的最靈活的線程間同步方式,事件可以處于激發(fā)狀態(tài)(signaled or true)或未激發(fā)狀態(tài)(unsignal or false),。根據(jù)狀態(tài)變遷方式的不同,,事件可分為兩類: (1)手動設置:這種對象只可能用程序手動設置,,在需要該事件或者事件發(fā)生時,,采用SetEvent及ResetEvent來進行設置。 ?。?)自動恢復:一旦事件發(fā)生并被處理后,,自動恢復到?jīng)]有事件狀態(tài),不需要再次設置。 創(chuàng)建事件的函數(shù)原型為:
使用"事件"機制應注意以下事項: (1)如果跨進程訪問事件,,必須對事件命名,,在對事件命名的時候,要注意不要與系統(tǒng)命名空間中的其它全局命名對象沖突,; ?。?)事件是否要自動恢復; ?。?)事件的初始狀態(tài)設置,。 由于event對象屬于內(nèi)核對象,故進程B可以調(diào)用OpenEvent函數(shù)通過對象的名字獲得進程A中event對象的句柄,,然后將這個句柄用于ResetEvent,、SetEvent和WaitForMultipleObjects等函數(shù)中。此法可以實現(xiàn)一個進程的線程控制另一進程中線程的運行,,例如:
臨界區(qū) 定義臨界區(qū)變量
通常情況下,,CRITICAL_SECTION結(jié)構體應該被定義為全局變量,以便于進程中的所有線程方便地按照變量名來引用該結(jié)構體,。 初始化臨界區(qū)
該函數(shù)用于對pcs所指的CRITICAL_SECTION結(jié)構體進行初始化,。該函數(shù)只是設置了一些成員變量,它的運行一般不會失敗,,因此它采用了VOID類型的返回值,。該函數(shù)必須在任何線程調(diào)用EnterCriticalSection函數(shù)之前被調(diào)用,如果一個線程試圖進入一個未初始化的CRTICAL_SECTION,,那么結(jié)果將是很難預計的,。 刪除臨界區(qū)
進入臨界區(qū)
離開臨界區(qū)
使用臨界區(qū)編程的一般方法是:
關于臨界區(qū)的使用,有下列注意點: ?。?)每個共享資源使用一個CRITICAL_SECTION變量,; (2)不要長時間運行關鍵代碼段,,當一個關鍵代碼段長時間運行時,,其他線程就會進入等待狀態(tài),這會降低應用程序的運行性能,; ?。?)如果需要同時訪問多個資源,則可能連續(xù)調(diào)用EnterCriticalSection,; ?。?)Critical Section不是OS核心對象,,如果進入臨界區(qū)的線程"掛"了,將無法釋放臨界資源,。這個缺點在Mutex中得到了彌補,。 互斥 互斥量的作用是保證每次只能有一個線程獲得互斥量而得以繼續(xù)執(zhí)行,使用CreateMutex函數(shù)創(chuàng)建:
Mutex是核心對象,,可以跨進程訪問,,下面的代碼給出了從另一進程訪問命名Mutex的例子:
相關API:
使用互斥編程的一般方法是:
互斥(mutex)內(nèi)核對象能夠確保線程擁有對單個資源的互斥訪問權?;コ鈱ο蟮男袨樘匦耘c臨界區(qū)相同,,但是互斥對象屬于內(nèi)核對象,而臨界區(qū)則屬于用戶方式對象,,因此這導致mutex與Critical Section的如下不同: ?。?) 互斥對象的運行速度比關鍵代碼段要慢; ?。?) 不同進程中的多個線程能夠訪問單個互斥對象; ?。?) 線程在等待訪問資源時可以設定一個超時值,。 下圖更詳細地列出了互斥與臨界區(qū)的不同: 信號量
信號量是維護0到指定最大值之間的同步對象。信號量狀態(tài)在其計數(shù)大于0時是有信號的,,而其計數(shù)是0時是無信號的,。信號量對象在控制上可以支持有限數(shù)量共享資源的訪問。 信號量的特點和用途可用下列幾句話定義: ?。?)如果當前資源的數(shù)量大于0,,則信號量有效; ?。?)如果當前資源數(shù)量是0,,則信號量無效; ?。?)系統(tǒng)決不允許當前資源的數(shù)量為負值,; (4)當前資源數(shù)量決不能大于最大資源數(shù)量,。 創(chuàng)建信號量
釋放信號量 通過調(diào)用ReleaseSemaphore函數(shù),,線程就能夠?qū)π艠说漠斍百Y源數(shù)量進行遞增,該函數(shù)原型為:
打開信號量 和其他核心對象一樣,,信號量也可以通過名字跨進程訪問,,打開信號量的API為:
互鎖訪問 當必須以原子操作方式來修改單個值時,,互鎖訪問函數(shù)是相當有用的。所謂原子訪問,,是指線程在訪問資源時能夠確保所有其他線程都不在同一時間內(nèi)訪問相同的資源,。 請看下列代碼:
運行ThreadFunc1和ThreadFunc2線程,結(jié)果是不可預料的,,因為globalVar++并不對應著一條機器指令,,我們看看globalVar++的反匯編代碼:
在"mov eax,[globalVar (0042d3f0)]" 指令與"add eax,1" 指令以及"add eax,1" 指令與"mov [globalVar (0042d3f0)],eax"指令之間都可能發(fā)生線程切換,使得程序的執(zhí)行后globalVar的結(jié)果不能確定,。我們可以使用InterlockedExchangeAdd函數(shù)解決這個問題:
InterlockedExchangeAdd保證對變量globalVar的訪問具有"原子性",。互鎖訪問的控制速度非???,調(diào)用一個互鎖函數(shù)的CPU周期通常小于50,不需要進行用戶方式與內(nèi)核方式的切換(該切換通常需要運行1000個CPU周期),。 互鎖訪問函數(shù)的缺點在于其只能對單一變量進行原子訪問,,如果要訪問的資源比較復雜,仍要使用臨界區(qū)或互斥,。 可等待定時器 可等待定時器是在某個時間或按規(guī)定的間隔時間發(fā)出自己的信號通知的內(nèi)核對象,。它們通常用來在某個時間執(zhí)行某個操作。 創(chuàng)建可等待定時器
設置可等待定時器 可等待定時器對象在非激活狀態(tài)下被創(chuàng)建,,程序員應調(diào)用 SetWaitableTimer函數(shù)來界定定時器在何時被激活:
取消可等待定時器
打開可等待定時器 作為一種內(nèi)核對象,,WaitableTimer也可以被其他進程以名字打開:
實例 下面給出的一個程序可能發(fā)生死鎖現(xiàn)象:
運行這個程序,在中途一旦發(fā)生這樣的輸出: 線程1占用臨界區(qū)1 線程2占用臨界區(qū)2 或 線程2占用臨界區(qū)2 線程1占用臨界區(qū)1 或 線程1占用臨界區(qū)2 線程2占用臨界區(qū)1 或 線程2占用臨界區(qū)1 線程1占用臨界區(qū)2 程序就"死"掉了,,再也運行不下去,。因為這樣的輸出,意味著兩個線程相互等待對方釋放臨界區(qū),,也即出現(xiàn)了死鎖,。 如果我們將線程2的控制函數(shù)改為:
再次運行程序,死鎖被消除,,程序不再擋掉,。這是因為我們改變了線程2中獲得臨界區(qū)1、2的順序,,消除了線程1,、2相互等待資源的可能性。 由此我們得出結(jié)論,,在使用線程間的同步機制時,,要特別留心死鎖的發(fā)生。 |
|