【轉(zhuǎn)】【W(wǎng)indows】線程漫談——線程同步之信號(hào)量和互斥量
Posted on 2012-07-13 22:54 小胡子 閱讀(243) 評(píng)論(0) 編輯 收藏本系列意在記錄Windwos線程的相關(guān)知識(shí)點(diǎn),包括線程基礎(chǔ)、線程調(diào)度、線程同步、TLS、線程池等
信號(hào)量?jī)?nèi)核對(duì)象
信號(hào)量?jī)?nèi)核對(duì)象用來(lái)進(jìn)行資源計(jì)數(shù),它包含一個(gè)使用計(jì)數(shù)、最大資源數(shù)、當(dāng)前資源計(jì)數(shù)。最大資源數(shù)表示信號(hào)量可以控制的最大資源數(shù)量,當(dāng)前資源數(shù)表示信號(hào)當(dāng)前可用的資源數(shù)量。
設(shè)想一個(gè)場(chǎng)景:需要開(kāi)發(fā)一個(gè)服務(wù)器進(jìn)程,最多同時(shí)運(yùn)行5個(gè)線程來(lái)響應(yīng)客戶端請(qǐng)求,應(yīng)該設(shè)計(jì)一個(gè)“線程池”。最開(kāi)始的時(shí)候,5個(gè)線程都應(yīng)該在等待狀 態(tài),如果有一個(gè)客戶端請(qǐng)求到來(lái),那么喚醒其中的一個(gè)線程以處理客戶端請(qǐng)求,如果同時(shí)的請(qǐng)求數(shù)量為5,那么5個(gè)線程將全部投入使用,再多的請(qǐng)求應(yīng)該被放棄。 也就是說(shuō),隨著客戶端請(qǐng)求的增加,當(dāng)前資源計(jì)數(shù)隨之遞減。
我們可能需要這樣的一個(gè)內(nèi)核對(duì)象來(lái)實(shí)現(xiàn)這個(gè)功能:初始化5個(gè)線程并同時(shí)等待一個(gè)內(nèi)核對(duì)象觸發(fā),當(dāng)一個(gè)客戶端請(qǐng)求到來(lái)時(shí),試圖觸發(fā)內(nèi)核對(duì)象,這樣5個(gè) 線程中隨機(jī)一個(gè)被喚醒,并且自動(dòng)使內(nèi)核對(duì)象變?yōu)槲从|發(fā)。外部判斷上限是否到達(dá)5。表面看來(lái)似乎用“自動(dòng)重置的事件對(duì)象”即可實(shí)現(xiàn)這個(gè)功能啊,為什么要涉及 到信號(hào)量呢?因?yàn)樾盘?hào)量還可以控制一次喚醒多少個(gè)線程?。《疫@個(gè)例子只是信號(hào)量的一個(gè)用途,后面我們會(huì)看到一個(gè)更實(shí)際的用途。
總結(jié)一下,信號(hào)量?jī)?nèi)核對(duì)象是這樣的一種對(duì)象:它維護(hù)一個(gè)資源計(jì)數(shù),當(dāng)資源計(jì)數(shù)大于0,處于觸發(fā)狀態(tài);資源計(jì)數(shù)等于0時(shí),處于未觸發(fā)狀態(tài);資源計(jì)數(shù)不可能小于0,也絕不可能大于資源計(jì)數(shù)上限。下圖展示了這種內(nèi)核對(duì)象的特點(diǎn):
如上圖,只有資源計(jì)數(shù)>0時(shí)才是觸發(fā)狀態(tài),資源=0時(shí)為未觸發(fā)狀態(tài),而WaitForSingleObject成功將遞減資源計(jì)數(shù),調(diào)用ReleaseSemaphore將增加資源計(jì)數(shù)。
下面兩個(gè)函數(shù)CreateSemaphore和CreateSemaphoreEx用于創(chuàng)建信號(hào)量對(duì)象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | HANDLE WINAPI CreateSemaphore( __in_opt LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, //內(nèi)核對(duì)象安全描述符 __in LONG lInitialCount, //資源計(jì)數(shù)的初始值 __in LONG lMaximumCount, //資源計(jì)數(shù)的最大值 __in_opt LPCTSTR lpName //內(nèi)核對(duì)象命名 ); HANDLE WINAPI CreateSemaphoreEx( __in_opt LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, __in LONG lInitialCount, __in LONG lMaximumCount, __in_opt LPCTSTR lpName, __reserved DWORD dwFlags, __in DWORD dwDesiredAccess ); |
任何進(jìn)程可以用OpenSemaphore來(lái)得到一個(gè)命名的信號(hào)量:
1 2 3 4 5 | HANDLE WINAPI OpenSemaphore( __in DWORD dwDesiredAccess, __in BOOL bInheritHandle, __in LPCTSTR lpName ); |
線程通過(guò)調(diào)用ReleaseSemaphore來(lái)遞增資源計(jì)數(shù),不一定每次只遞增1,可以設(shè)置遞增任意值。當(dāng)將要超過(guò)資源上限值的時(shí)候,ReleaseSemaphore會(huì)返回FALSE。
1 2 3 4 5 | BOOL WINAPI ReleaseSemaphore( __in HANDLE hSemaphore, __in LONG lReleaseCount, //可以設(shè)置遞增的值 __out_opt LPLONG lpPreviousCount //返回先前的資源計(jì)數(shù) ); |
互斥量?jī)?nèi)核對(duì)象
互斥量(mutex)用來(lái)確保一個(gè)線程獨(dú)占對(duì)一個(gè)資源的訪問(wèn)?;コ饬堪粋€(gè)使用計(jì)數(shù)、線程ID和一個(gè)遞歸計(jì)數(shù),互斥量與關(guān)鍵段的行為幾乎相同(因 為它記錄了線程ID和遞歸計(jì)數(shù),使得互斥量可以支持遞歸調(diào)用的情況)。互斥量的規(guī)則十分簡(jiǎn)單:如果線程ID為0(即沒(méi)有線程獨(dú)占它),那么它處于觸發(fā)狀 態(tài),任何試圖等待該對(duì)象的線程都將獲得資源的獨(dú)占訪問(wèn);如果線程ID不為0,那么它處于未觸發(fā)狀態(tài),任何試圖等待該對(duì)象的線程都將等待。
可以使用CreateMutex或者CreateMutexEx創(chuàng)建互斥對(duì)象:
1 2 3 4 5 6 7 8 9 10 11 12 | HANDLE WINAPI CreateMutex( __in_opt LPSECURITY_ATTRIBUTES lpMutexAttributes, __in BOOL bInitialOwner, //初始化對(duì)象的狀態(tài),如果傳入FALSE則會(huì)初始化為觸發(fā)狀態(tài),如果傳入TRUE,那么對(duì)象的線程ID會(huì)被設(shè)置成當(dāng)前調(diào)用線程,并初始化為未觸發(fā) __in_opt LPCTSTR lpName ); HANDLE WINAPI CreateMutexEx( __in_opt LPSECURITY_ATTRIBUTES lpMutexAttributes, __in_opt LPCTSTR lpName, __in DWORD dwFlags, __in DWORD dwDesiredAccess ); |
一如既往,OpenMutex用于打開(kāi)一個(gè)已經(jīng)命名的互斥量?jī)?nèi)核對(duì)象:
1 2 3 4 5 | HANDLE WINAPI OpenMutex( __in DWORD dwDesiredAccess, __in BOOL bInheritHandle, __in LPCTSTR lpName ); |
線程在獲得對(duì)獨(dú)占資源的訪問(wèn)權(quán)限之后,可以正常執(zhí)行相關(guān)的邏輯,當(dāng)需要釋放互斥對(duì)象的時(shí)候可以調(diào)用ReleaseMutex:
1 2 3 | BOOL WINAPI ReleaseMutex( __in HANDLE hMutex ); |
互斥量與其他內(nèi)核對(duì)象不同,它會(huì)記錄究竟是哪個(gè)線程占用了共享資源,結(jié)合遞歸計(jì)數(shù),同一個(gè)線程可以在獲得共享資源之后繼續(xù)訪問(wèn)共享資源,這個(gè)行為就像關(guān)鍵段一樣。然而互斥量和關(guān)鍵段從本質(zhì)上是不同的,關(guān)鍵段是用戶模式的線程同步方法,而互斥量是內(nèi)核模式的線程同步方式。
介紹完這兩個(gè)內(nèi)核對(duì)象后,我們思考一下前面在【W(wǎng)indows】線程漫談——線程同步之Slim讀/寫(xiě)鎖中 設(shè)計(jì)的一個(gè)場(chǎng)景:有一個(gè)共享的隊(duì)列,2個(gè)服務(wù)端線程負(fù)責(zé)讀取隊(duì)列中的條目以處理,2個(gè)客戶端線程負(fù)責(zé)寫(xiě)入隊(duì)列中的條目以使服務(wù)先端線程處理,當(dāng)隊(duì)列中沒(méi)有 條目的時(shí)候應(yīng)當(dāng)掛起服務(wù)端線程,直到有條目進(jìn)入時(shí)才被喚醒,另一方面,當(dāng)隊(duì)列已滿時(shí),客戶端線程應(yīng)當(dāng)掛起直到服務(wù)端至少處理了一個(gè)條目,以釋放至少一個(gè)條 目的空間。
現(xiàn)在我們來(lái)用信號(hào)量和互斥量來(lái)實(shí)現(xiàn)同樣的功能,下面的流程圖分別是客戶端寫(xiě)入線程和服務(wù)端讀取線程的邏輯:
1.首先創(chuàng)建一個(gè)互斥量對(duì)象m_hmtxQ,并初始化為未觸發(fā)狀態(tài);之后創(chuàng)建一個(gè)信號(hào)量對(duì)象,并設(shè)置最大資源計(jì)數(shù)為隊(duì)列的長(zhǎng)度,初始化資源計(jì)數(shù)為0,正好表征隊(duì)列元素的個(gè)數(shù)。
1 2 | m_hmtxQ = CreateMutex(NULL,FALSE,NULL); m_hsemNumElements = CreateSemaphore(NULL,0,nMaxElements,NULL); |
2.設(shè)計(jì)客戶端核心邏輯如下圖:
WatiForSingleObject:試圖獲得隊(duì)列的獨(dú)占訪問(wèn)權(quán)限,對(duì)于這個(gè)隊(duì)列無(wú)論是讀還是寫(xiě)都應(yīng)該是線程獨(dú)占的。因此,使用互斥量對(duì)象來(lái)同步;
ReleaseSemaphore:試圖增加一個(gè)資源計(jì)數(shù),表征客戶端想要向隊(duì)列中增加一個(gè)元素,當(dāng)然隊(duì)列可能現(xiàn)在已經(jīng)滿了,對(duì)應(yīng)的資源計(jì)數(shù)已達(dá)到 計(jì)數(shù)上限,此時(shí)ReleaseSemaphore會(huì)返回FALSE,這樣客戶端就不能像隊(duì)列中插入元素。反之,如果ReleaseSemaphore返回 TRUE,表示隊(duì)列沒(méi)有滿,客戶端可以向隊(duì)列中插入元素。
ReleaseMutex:無(wú)論客戶端是否能夠像隊(duì)列中插入元素,在結(jié)束訪問(wèn)后,都應(yīng)該釋放互斥對(duì)象,以便其他線程能夠進(jìn)入臨界資源。
3.設(shè)計(jì)服務(wù)端核心邏輯如下圖:
WatiForSingleObject:試圖獲得隊(duì)列的獨(dú)占訪問(wèn)權(quán)限,對(duì)于這個(gè)隊(duì)列無(wú)論是讀還是寫(xiě)都應(yīng)該是線程獨(dú)占的。因此,使用互斥量對(duì)象來(lái)同步;
WaitForSingleObject(m_hsemNumElements…):試圖檢查信號(hào)量對(duì)象是否是觸發(fā)狀態(tài)。只有是觸發(fā)狀態(tài)的信號(hào)量對(duì) 象,線程才能進(jìn)入;也就意味著:隊(duì)列中只要有元素(資源>0,觸發(fā)狀態(tài)),服務(wù)端就能讀取。反之,如果隊(duì)列中沒(méi)有元素(資源=0,未觸發(fā)狀態(tài)),服 務(wù)端將暫時(shí)不能訪問(wèn)隊(duì)列,這時(shí)應(yīng)該立即釋放Mutex。
ReleaseMutex:無(wú)論客戶端是否能夠像隊(duì)列中插入元素,在結(jié)束訪問(wèn)后,都應(yīng)該釋放互斥對(duì)象,以便其他線程能夠進(jìn)入臨界資源。
原文:http://www.cnblogs.com/P_Chou/archive/2012/07/13/semaphore-and-mutex-in-thread-sync.html