用户模式下的线程同步
在以下兩種基本情況下,線程之間需要相互通信
1.需要讓多個(gè)線程同時(shí)訪問(wèn)一個(gè)共享資源,同時(shí)不能破壞資源的完整性
2.一個(gè)線程需要通知其他線程某項(xiàng)任務(wù)已經(jīng)完成。
原子訪問(wèn)相關(guān)的內(nèi)容就直接略過(guò)了,因?yàn)楦杏X(jué)實(shí)際使用的過(guò)程中并不多。
下面直接開(kāi)始說(shuō)一下關(guān)鍵段,它在執(zhí)行之前需要獨(dú)占對(duì)一些共享資源的訪問(wèn)權(quán)。這種方式可以讓多行代碼以原子方式來(lái)對(duì)資源進(jìn)行操控。這里的原子方式,指的是代碼知道除了當(dāng)前線程之外,沒(méi)有其他任何線程會(huì)同時(shí)訪問(wèn)該資源。在當(dāng)前線程離開(kāi)關(guān)鍵段之前,系統(tǒng)是不會(huì)去調(diào)度任何想要訪問(wèn)同一資源的其他線程的。
一般是EnterCriticalSection和LeaveCriticalScetion配對(duì)使用,需要先創(chuàng)建一個(gè)CRITICAL_SECTION結(jié)構(gòu)。
這結(jié)構(gòu)一般都是作為全局變量來(lái)使用,也可以作為局部變量來(lái)分配或者堆中,另外作為類的一個(gè)私有字段還分配也是很常見(jiàn)的。
在使用CRITICAL_SECTION的時(shí)候,只有兩個(gè)必要條件,第一個(gè)是所有想要訪問(wèn)資源的線程必須知道用來(lái)保護(hù)資源的CRITICAL_SECTION結(jié)構(gòu)的地址,第二個(gè)是任何線程試圖訪問(wèn)被保護(hù)的資源之前,必須對(duì)CRITICAL_SECTION結(jié)構(gòu)的內(nèi)部成員進(jìn)行初始化。
下面的函數(shù)用來(lái)進(jìn)行初始化
void InitializerCriticalSection(PCRITICAL_SECTION pcs);
當(dāng)線程不在需要訪問(wèn)共享資源的時(shí)候,應(yīng)該調(diào)用下面的函數(shù)來(lái)清理結(jié)構(gòu)
void deleteCriticalSection(PCRITICAL_SECTION pcs);
EnterCriticalSection會(huì)檢查結(jié)構(gòu)體中的成員變量,這些變量表示是否有線程正在訪問(wèn)資源,以及哪個(gè)線程正在訪問(wèn)資源。EnterCriticalSection會(huì)執(zhí)行下面的測(cè)試:
1.如果沒(méi)有線程正在訪問(wèn)資源,那么EnterCriticalSection會(huì)更新成員變量,以表示調(diào)用線程已經(jīng)獲準(zhǔn)對(duì)資源的訪問(wèn),并立即訪問(wèn),這樣線程就可以繼續(xù)執(zhí)行
2.如果成員變量表示調(diào)用線程已經(jīng)獲準(zhǔn)訪問(wèn)資源,那么EnterCriticalSection會(huì)更新變量,以表示調(diào)用線程被獲準(zhǔn)訪問(wèn)的次數(shù),并立即放回,這樣線程就可以繼續(xù)執(zhí)行。這樣的情況非常少見(jiàn),只有當(dāng)線程調(diào)用LeaveCriticalSection之前連續(xù)調(diào)用EnterCriticalSection兩次以上才會(huì)發(fā)生。
3.如果成員變量表示有一個(gè)(調(diào)用線程之外的其他)線程已經(jīng)獲準(zhǔn)訪問(wèn)資源,那么EnterCriticalSection會(huì)使用一個(gè)事件內(nèi)核對(duì)象來(lái)吧線程切換到等待狀態(tài)。一旦當(dāng)前正在訪問(wèn)資源的線程調(diào)用了LeaveCriticalSection,系統(tǒng)會(huì)自動(dòng)更新結(jié)構(gòu)的成員變量并將等待中的線程切換回可調(diào)度狀態(tài)。
我們也可以用下面的函數(shù)來(lái)代替EnterCriticalSection
Bool TryEnterCriticalSection(PCRITICAL_SECTION pcs);
TryEnterCriticalSection從來(lái)不會(huì)讓調(diào)用線程進(jìn)入等待狀態(tài),他會(huì)通過(guò)返回值來(lái)表示調(diào)用線程是否獲準(zhǔn)訪問(wèn)資源,因此如果TryEnterCriticalSection發(fā)現(xiàn)資源正在被其他線程訪問(wèn),那么它會(huì)返回false。每一個(gè)返回true的TryEnterCriticalSection調(diào)用必須有一個(gè)對(duì)應(yīng)的LeaveCriticalSection。
當(dāng)線程試圖進(jìn)入一個(gè)關(guān)鍵段,但這關(guān)鍵段正在被另一個(gè)線程占用的時(shí)候,函數(shù)會(huì)立即把調(diào)用線程切換到等待狀態(tài)。這意味著線程必須從用戶模式切換到內(nèi)核模式,這個(gè)切換的開(kāi)銷非常大。為了提高性能,可以將旋轉(zhuǎn)鎖合并到關(guān)鍵段中,因此當(dāng)調(diào)用EnterCriticalSection的時(shí)候,他會(huì)用一個(gè)旋轉(zhuǎn)鎖不斷的循環(huán),嘗試在一段時(shí)間內(nèi)獲取的對(duì)資源的訪問(wèn)權(quán),只有當(dāng)嘗試失敗的時(shí)候買線程才會(huì)切換到內(nèi)核狀態(tài)并進(jìn)到等待狀態(tài)。
為了在是因關(guān)鍵段的時(shí)候同時(shí)使用旋轉(zhuǎn)鎖,必須調(diào)用以下的函數(shù)來(lái)初始化關(guān)鍵段
Bool InitializeCriticalSectionAndSpinCount(PCRITICAL_SECTION pcs,DWORD dwSpinCount);
與InitializerCriticalSection相似,但是第二個(gè)參數(shù)是我們希望旋轉(zhuǎn)鎖循環(huán)的次數(shù),我們也可以通過(guò)下面的函數(shù)來(lái)改變關(guān)鍵段的旋轉(zhuǎn)次數(shù)
DWORD WINAPI SetCriticalSectionSpinCount(_Inout_?LPCRITICAL_SECTION lpCriticalSection,_In_????DWORD ?????????????dwSpinCount );用來(lái)保護(hù)進(jìn)程對(duì)的關(guān)鍵段所使用的旋轉(zhuǎn)次數(shù)大約是4000,這里作為參考。
SRWLock的目的和關(guān)鍵段系統(tǒng),對(duì)一個(gè)資源進(jìn)行保護(hù),不讓其他資源訪問(wèn)他。但是與關(guān)鍵段不同,SRWLock允許我們區(qū)分哪些想要讀取資源的值的線程(讀取者線程)和想要更新資源的值的線程(寫(xiě)入者線程)。讓所有的讀取者線程在同一時(shí)刻訪問(wèn)共享資源應(yīng)該是可行的,只有當(dāng)寫(xiě)入者線程需要對(duì)資源進(jìn)行更新的時(shí)候才需要同步,在這樣子情況下,寫(xiě)入者線程應(yīng)該獨(dú)占對(duì)資源的訪問(wèn)權(quán):任何其他線程。
首先我們需要分配一個(gè)SRWLOCK結(jié)構(gòu)并用InitialSRWLock函數(shù)來(lái)對(duì)他進(jìn)行初始化
VOID WINAPI InitializeSRWLock(?_Out_? PSRWLOCK SRWLock? );?
一旦SRWLock的初始化完成后,寫(xiě)入者線程就可以調(diào)用AcquireSRWLockExclusive,將SRWLOCK對(duì)象的地址作為參數(shù)傳入,以嘗試獲得對(duì)被保護(hù)資源的獨(dú)占訪問(wèn)權(quán)。
VOID WINAPI AcquireSRWLockExclusive(? _Inout_? PSRWLOCK SRWLock? );?
在完成對(duì)資源的更新之后。應(yīng)該調(diào)用ReleaseSRWLockExclusive函數(shù),并將SRWLOCK對(duì)象的地址作為參數(shù)傳入,解除對(duì)資源的鎖定。
VOID WINAPI ReleaseSRWLockExclusive(?? _Inout_? PSRWLOCK SRWLock? );?
讀取者的操作
讀取者調(diào)用的兩個(gè)參數(shù)是:
VOID AcquireSRWLockShared(PSRWLOCK SRWLock);??
VOID ReleaseSRWLockShared(PSRWLOCK SRWLock);??
不存在刪除或銷毀SRWLock的函數(shù),系統(tǒng)會(huì)自動(dòng)執(zhí)行清理工作。
與關(guān)鍵段相比,SRWLock缺乏下面兩個(gè)特性:
(1)不存在TryEnter(Shared/Exclusive)SRWLock之類的函數(shù)。如果鎖已經(jīng)被占用,那么調(diào)用AcquireSRWLock(SHared/Exclusive)會(huì)阻塞調(diào)用線程。
(2)不能遞歸調(diào)用SRWLOCK。一個(gè)線程不能為了多次寫(xiě)入資源而多次鎖定資源,然后多次調(diào)用ReleaseSRWLock來(lái)釋放對(duì)資源的鎖定。
但是,如果可以接受這些限制,就可以用SRWLock來(lái)代替關(guān)鍵段,并獲得實(shí)際性能和可伸縮性的提升。
三、同步機(jī)制性能的比較
通過(guò)一個(gè)簡(jiǎn)單的基準(zhǔn)測(cè)試可以比較各種同步機(jī)制的性能:產(chǎn)生1、2和4個(gè)線程,使用不同的同步機(jī)制重復(fù)執(zhí)行相同的任務(wù),在雙處理器上運(yùn)行,得出的結(jié)果是:
用戶模式下同步機(jī)制性能的對(duì)比實(shí)驗(yàn)結(jié)果如下(計(jì)數(shù)單位:微秒)
| 線程數(shù) | Volatile Read | Volatile Write | Interlocked Increment | Critical Section | SRWLock Shared | SRWLock Exclusive | Mutex |
| 1 | 8 | 8 | 35 | 66 | 66 | 67 | 1060 |
| 2 | 8 | 76 | 153 | 268 | 134 | 148 | 11082 |
| 4 | 9 | 145 | 361 | 768 | 244 | 307 | 23785 |
各種機(jī)制對(duì)比:
(1)讀取volatile長(zhǎng)整型值,讀取非常快,因?yàn)椴恍枰M(jìn)行任何同步,與CPU的高速緩存完全無(wú)關(guān)。
(2)寫(xiě)入volatile長(zhǎng)整型值。單線程的時(shí)間和讀取差不多,但是雙線程的時(shí)候時(shí)間不只是加倍,這是因?yàn)镃PU之間必須相互通信以維護(hù)高速緩存的一致性。如果機(jī)器有更多的CPU,那么性能還會(huì)下降,因?yàn)樾枰诟嗟腃PU之間進(jìn)行通信來(lái)使得所有CPU的高速緩存一致。
(3)使用InterlockedIncrement來(lái)安全遞增一個(gè)volatile長(zhǎng)整型值。
它比第一種方法要慢,這是因?yàn)镃PU必須鎖定內(nèi)存。使用兩個(gè)線程要比一個(gè)線程慢得多,這是因?yàn)楸仨氃趦蓚€(gè)CPU之間來(lái)回傳輸數(shù)據(jù)以維護(hù)高速緩存的一致性。
(4)使用關(guān)鍵段來(lái)讀取一個(gè)volatile長(zhǎng)整型值。
關(guān)鍵段比較慢,是因?yàn)槲覀儽仨毾冗M(jìn)入再離開(kāi)。進(jìn)入和離開(kāi)需要修改CRITICAL_SECTION結(jié)果中的多個(gè)字段。4個(gè)線程需要花費(fèi)更多時(shí)間,是因?yàn)樯舷挛那袚Q增大了發(fā)生爭(zhēng)奪現(xiàn)象的可能性。
(5)使用SRWLock來(lái)讀取一個(gè)volatile長(zhǎng)整型值。
當(dāng)有多個(gè)線程的時(shí)候,讀操作比寫(xiě)操作快。由于多個(gè)線程會(huì)不斷地寫(xiě)入鎖的字段以及它保護(hù)的數(shù)據(jù),因此各CPU必須在它們的高速緩存之間來(lái)回傳輸數(shù)據(jù)。
它的性能和關(guān)鍵段差不多,但是很多時(shí)候要優(yōu)于關(guān)鍵段。建議的做法是用SRWLock替代關(guān)鍵段。
(6)使用同步內(nèi)核對(duì)象互斥量。
互斥量是目前性能最差的,是因?yàn)榈却コ饬恳约昂髞?lái)釋放互斥量需要線程每次在用戶模式和內(nèi)核模式之間卻換,開(kāi)銷很大。
總結(jié):應(yīng)該首先嘗試不要共享數(shù)據(jù),然后依次使用volatile讀取、volatile寫(xiě)入、Interlocked函數(shù)、SRWLock以及關(guān)鍵段。僅當(dāng)這些都不能滿足要求的時(shí)候,再使用內(nèi)核對(duì)象。
Windows提供SleepConditionVariableCS或SleepConditionVariableSRW函數(shù),等待條件變量。線程在等待該條件變量時(shí),會(huì)以原子方式把鎖釋放并將自己阻塞,直到該條件變量被觸發(fā)時(shí)為止。
Bool?SleepConditionVariableCS(??PCONDITION_VARIABLE?pConditionVariable,??PCRITICAL_SECTION?pCriticalSection,??DWORD?dwMilliseconds);??Bool?SleepConditionVariableSRW(??PCONDITION_VARIABLE?pConditionVariable,??PSRWLOCK?pSRWLock,??DWORD?dwMilliseconds??ULONG?Flags);?pConditonVariable指向一個(gè)以初始化的條件變量,調(diào)用線程將等待該條件變量。第二個(gè)參數(shù)指向一個(gè)關(guān)鍵段或是SRWLock對(duì)象。該關(guān)鍵段或SRWLock用來(lái)同步對(duì)共享資源的訪問(wèn)。Flags指定一旦條件變量被觸發(fā),線程將以何種方式獲得鎖。對(duì)讀取者線程來(lái)說(shuō)應(yīng)該傳入CONDITION_VARIABLE_LOCKMODE_SHARED表示希望共享對(duì)資源的訪問(wèn)。對(duì)于寫(xiě)入者線程應(yīng)該傳入0,表示獨(dú)占資源。
dwMilliseconds表示我們希望線程花多少時(shí)間來(lái)等待條件被觸發(fā)。在指定的時(shí)間用完時(shí),如果條件變量尚未被觸發(fā),函數(shù)返回false,否則為true。
當(dāng)另一個(gè)線程檢測(cè)到相應(yīng)的條件已經(jīng)滿足時(shí),比如存在一個(gè)元素可以讓讀取者線程讀取。它會(huì)調(diào)用WakeConditionVariable或WakeAllConditionVariable,觸發(fā)條件變量。這樣調(diào)用Sleep*函數(shù)而阻塞在該條件變量的線程就會(huì)被喚醒。
總結(jié)
以上是生活随笔為你收集整理的用户模式下的线程同步的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: C#异步
- 下一篇: libjpeg(2)