多線程內(nèi)容大致分兩部分,其一是異步操作,可通過(guò)專(zhuān)用,線程池,task,parallel,plinq等,而這里又涉及工作線程與io線程;其二是線程同步問(wèn)題,鄙人現(xiàn)在學(xué)習(xí)與探究的是線程同步問(wèn)題。
通過(guò)學(xué)習(xí)《clr via c#》里面的內(nèi)容,對(duì)線程同步形成了脈絡(luò)較清晰的體系結(jié)構(gòu),在多線程中實(shí)現(xiàn)線程同步的是線程同步構(gòu)造,這個(gè)構(gòu)造分兩大類(lèi),一個(gè)是基元構(gòu)造,一個(gè)是混合構(gòu)造。所謂基元?jiǎng)t是在代碼中使用最簡(jiǎn)單的構(gòu)造?;瓨?gòu)造又分成兩類(lèi),一個(gè)是用戶(hù)模式,另一個(gè)是內(nèi)核模式。而混合構(gòu)造則是在內(nèi)部會(huì)使用基元構(gòu)造的用戶(hù)模式和內(nèi)核模式,使用它的模式會(huì)有一定的策略,因?yàn)橛脩?hù)模式和內(nèi)核模式各有利弊,混合構(gòu)造則是為了平衡兩者的利與弊而設(shè)計(jì)出來(lái)。下面則列舉整個(gè)線程同步體系結(jié)構(gòu)
基元
1.1 用戶(hù)模式
1.1.1 volatile
1.1.2 interlock
1.2 內(nèi)核模式
1.2.1 waithandle
1.2.2 manualresetevent與autoresetevent
1.2.3 semaphore
1.2.4 mutex
混合
2.1 各種slim
2.2 monitor
2.3 methodimplattribute與synchronizationattribute
2.4 readerwriterlock
2.5 barier(少用)
2.6 coutdownevent(少用)
先從線程同步問(wèn)題的原因說(shuō)起,當(dāng)內(nèi)存中有一個(gè)整形的變量a,里面存放的值是2,當(dāng)線程1執(zhí)行的時(shí)候它會(huì)把a(bǔ)的值從內(nèi)存中取出存放到cpu的寄存器中,并把a(bǔ)賦值為3,此時(shí)剛好線程1的時(shí)間片結(jié)束;接著cpu把時(shí)間片分給線程2,線程2同樣把a(bǔ)從內(nèi)存中的值取出來(lái)放到內(nèi)存中,但是由于線程1并沒(méi)有把變量a的新值3放回內(nèi)存,故線程2讀到的仍然是舊的值(也就是臟數(shù)據(jù))2,然后線程2要是需要對(duì)a值進(jìn)行一些判斷之類(lèi)的就會(huì)出現(xiàn)一些非預(yù)期的結(jié)果了。
而針對(duì)上面這種對(duì)資源的共享問(wèn)題處理,往往會(huì)使用各種各樣辦法。下面則逐一介紹
先說(shuō)說(shuō)基元構(gòu)造中的用戶(hù)模式,凡是用戶(hù)模式的優(yōu)點(diǎn)是它的執(zhí)行相對(duì)較快,因?yàn)樗峭ㄟ^(guò)一系列cpu指令來(lái)協(xié)調(diào),它造成的阻塞只是極短時(shí)間的阻塞,對(duì)操作系統(tǒng)而言這個(gè)線程是一直在運(yùn)行,從未被阻塞。缺點(diǎn)就是唯有系統(tǒng)內(nèi)核才能停止這樣的一個(gè)線程運(yùn)行。另一方面就是由于線程在自旋而非阻塞,那么它還會(huì)占用這cpu的時(shí)間,造成對(duì)cpu時(shí)間的浪費(fèi)。
首先是基元用戶(hù)模式構(gòu)造中的volatile構(gòu)造,這個(gè)構(gòu)造網(wǎng)上很多說(shuō)法是讓cpu對(duì)指定字段(field,也就是變量)的讀都是從內(nèi)存讀,每次寫(xiě)都是往內(nèi)存寫(xiě)。然而它和編譯器的代碼優(yōu)化有關(guān)系。先看看如下代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class strageclass { vo int mflag = 0; int mvalue = 0; public void thread1() { mvalue = 5; mflag = 1; } public void thread2() { if (mflag == 1) console.writeline(mvalue); } } |
在懂得多線程同步問(wèn)題的同學(xué)們都會(huì)知道如果用兩個(gè)線程分別去執(zhí)行上面兩個(gè)方法時(shí),得出的結(jié)果有兩個(gè):
1.不輸出任何東西;
2.輸出5。但是在csc編譯器編譯成il語(yǔ)言或jit編譯成機(jī)器語(yǔ)言的過(guò)程中,會(huì)進(jìn)行代碼優(yōu)化,在方法thread1中,編譯器會(huì)覺(jué)得給兩個(gè)字段賦值會(huì)沒(méi)什么所謂,它只會(huì)站在單個(gè)線程執(zhí)行的角度來(lái)看,完全不會(huì)顧及多線程的問(wèn)題,因此它有可能會(huì)把兩行代碼的執(zhí)行順序調(diào)亂,導(dǎo)致先給mflag賦值為1,再給mvalue賦值為5,這就導(dǎo)致了第三種結(jié)果,輸出0。可惜這種結(jié)果我一直無(wú)法測(cè)試出來(lái)。
解決這個(gè)現(xiàn)象的就是volatile構(gòu)造,使用了這種構(gòu)造的效果是,凡是對(duì)使用了此構(gòu)造的字段進(jìn)行讀操作時(shí),該操作都保證在原有代碼順序下會(huì)在最先執(zhí)行;或者是凡是對(duì)使用了此構(gòu)造的字段進(jìn)行寫(xiě)操作時(shí),該操作都保證在原有代碼順序下會(huì)在最后執(zhí)行。
實(shí)現(xiàn)了volatile的構(gòu)造現(xiàn)在來(lái)說(shuō)有三個(gè),其一是thread的兩個(gè)靜態(tài)方法volatileread和volatilewrite,在msnd上的解析如下
thread.volatileread 讀取字段值。 無(wú)論處理器的數(shù)目或處理器緩存的狀態(tài)如何,該值都是由計(jì)算機(jī)的任何處理器寫(xiě)入的最新值。
thread.volatilewrite 立即向字段寫(xiě)入一個(gè)值,以使該值對(duì)計(jì)算機(jī)中的所有處理器都可見(jiàn)。
在多處理器系統(tǒng)上, volatileread 獲得由任何處理器寫(xiě)入的內(nèi)存位置的最新值。 這可能需要刷新處理器緩存;volatilewrite 確保寫(xiě)入內(nèi)存位置的值立即可見(jiàn)的所有處理器。 這可能需要刷新處理器緩存。
即使在單處理器系統(tǒng)上, volatileread 和 volatilewrite 確保值為讀取或?qū)懭雰?nèi)存,并不緩存 (例如,在處理器寄存器中)。 因此,您可以使用它們可以由另一個(gè)線程,或通過(guò)硬件更新的字段對(duì)訪問(wèn)進(jìn)行同步。
從上面的文字看不出他和代碼優(yōu)化有任何關(guān)聯(lián),那接著往下看。
volatile關(guān)鍵字則是volatile構(gòu)造的另外一種實(shí)現(xiàn)方式,它是volatileread和volatilewrite的簡(jiǎn)化版,使用 volatile 修飾符對(duì)字段可以保證對(duì)該字段的所有訪問(wèn)都使用 volatileread 或 volatilewrite。msdn中對(duì)volatile關(guān)鍵字的說(shuō)明是
volatile 關(guān)鍵字指示一個(gè)字段可以由多個(gè)同時(shí)執(zhí)行的線程修改。 聲明為 volatile 的字段不受編譯器優(yōu)化(假定由單個(gè)線程訪問(wèn))的限制。 這樣可以確保該字段在任何時(shí)間呈現(xiàn)的都是最新的值。
從這里可以看出跟代碼優(yōu)化有關(guān)系了。而縱觀上面的介紹得出兩個(gè)結(jié)論:
1.使用了volatile構(gòu)造的字段讀寫(xiě)都是直接對(duì)內(nèi)存操作,不涉及cpu寄存器,使得所有線程對(duì)它的讀寫(xiě)都是同步,不存在臟讀了。讀操作是原子的,寫(xiě)操作也是原子的。
2.使用了volatile構(gòu)造修飾(或訪問(wèn))字段,它會(huì)嚴(yán)格按照代碼編寫(xiě)的順序執(zhí)行,讀操作將會(huì)在最早執(zhí)行,寫(xiě)操作將會(huì)最遲執(zhí)行。
最后一個(gè)volatile構(gòu)造是在.net framework中新增的,里面包含的方法都是read和write,它實(shí)際上就相當(dāng)于thread的volatileread 和volatilewrite 。這需要拿源碼來(lái)說(shuō)明了,隨便拿一個(gè)volatile的read方法來(lái)看
而再看看thraed的volatileread方法
另一個(gè)用戶(hù)模式構(gòu)造是interlocked,這個(gè)構(gòu)造是保證讀和寫(xiě)都是在原子操作里面,這是與上面volatile最大的區(qū)別,volatile只能確保單純的讀或者單純的寫(xiě)。
為何interlocked是這樣,看一下interlocaked的方法就知道了
1
2
3
4
5
|
add( ref int , int ) // 調(diào)用externadd 外部方法 compareexchange( ref int32,int32,int32) //1與3是否相等,相等則替換2,返回1的原始值 decrement( ref int32) //遞減并返回 調(diào)用add exchange( ref int32,int32) //將2設(shè)置到1并返回 increment( ref int32) //自增 調(diào)用add |
就隨便拿其中一個(gè)方法add(ref int,int)來(lái)說(shuō)(increment和decrement這兩個(gè)方法實(shí)際上內(nèi)部調(diào)用了add方法),它會(huì)先讀到第一個(gè)參數(shù)的值,在與第二個(gè)參數(shù)求和后,把結(jié)果寫(xiě)到給第一參數(shù)中。首先這整個(gè)過(guò)程是一個(gè)原子操作,在這個(gè)操作里面既包含了讀,也包含了寫(xiě)。至于如何保證這個(gè)操作的原子性,估計(jì)需要查看rotor源碼才行。在代碼優(yōu)化方面來(lái)說(shuō),它確保了所有寫(xiě)操作都在interlocked之前去執(zhí)行,這保證了interlocked里面用到的值是最新的;而任何變量的讀取都在interlocked之后讀取,這保證了后面用到的值都是最新更改過(guò)的。
compareexchange方法相當(dāng)重要,雖然interlocked提供的方法甚少,但基于這個(gè)可以擴(kuò)展出其他更多方法,下面就是個(gè)例子,求出兩個(gè)值的最大值,直接抄了jeffrey的源碼
查看上面代碼,在進(jìn)入循環(huán)之前先聲明每次循環(huán)開(kāi)始時(shí)target的值,在求出最值之后,核對(duì)一下target的值是否有變化,如果有變化則需要再記錄新值,按照新值來(lái)再求一次最值,直到target不變?yōu)橹梗@就滿(mǎn)足了interlocked中所說(shuō)的,寫(xiě)都在interlocked之前發(fā)生,interlocked往后就能讀到最新的值。
基元內(nèi)核模式
內(nèi)核模式則是靠操作系統(tǒng)的內(nèi)核對(duì)象來(lái)處理線程的同步問(wèn)題。先說(shuō)其弊端,它的速度會(huì)相對(duì)慢。原因有兩個(gè),其一由于它是由操作系統(tǒng)內(nèi)核對(duì)象來(lái)實(shí)現(xiàn)的,需要操作系統(tǒng)內(nèi)部去協(xié)調(diào),另外一個(gè)原因是內(nèi)核對(duì)象都是一些非托管對(duì)象,在了解了appdomain之后就會(huì)知道,訪問(wèn)的對(duì)象不在當(dāng)前appdomain中的要么就進(jìn)行按值封送,要么就進(jìn)行按引用封送。經(jīng)過(guò)觀察這部分的非托管資源是按引用封送,這就會(huì)存在性能影響。綜合上面兩方面的兩點(diǎn)得出內(nèi)核模式的弊端。但是他也是有利的方面:1.線程在等待資源的時(shí)候不會(huì)"自旋"而是阻塞,這個(gè)節(jié)省了cpu時(shí)間,并且這個(gè)阻塞可以設(shè)定一個(gè)超時(shí)值。2.可以實(shí)現(xiàn)window線程和clr線程的同步,也可同步不同進(jìn)程中的線程(前者未體驗(yàn)到,而對(duì)于后者則知道semaphores中有邊界值資源)。3.可應(yīng)用安全性設(shè)置,為經(jīng)授權(quán)賬戶(hù)禁止訪問(wèn)(這個(gè)不知道是咋回事)。
內(nèi)核模式的所有對(duì)象的基類(lèi)是waithandle。內(nèi)核模式的所有類(lèi)層次如下
waithandle
eventwaithandle
autoresetevent
manualresetevent
semaphore
mutex
waithandle繼承marshalbyrefobject,這個(gè)就是按引用封送了非托管對(duì)象。waithandle里面主要是各種wait方法,調(diào)用了wait方法在沒(méi)有收到信號(hào)之前會(huì)被阻塞。waitone則是等待一個(gè)信號(hào),waitany(waithandle[] waithandles)則是收到任意一個(gè)waithandles的信號(hào),waitall(waithandle[] waithandles)則是等待所有waithandles的信號(hào)。這些方法都有一個(gè)版本允許設(shè)置一個(gè)超時(shí)時(shí)間。其他的內(nèi)核模式構(gòu)造都有類(lèi)似的wait方法。
eventwaithandle的內(nèi)部維護(hù)著一個(gè)布爾值,而wait方法會(huì)在這個(gè)布爾值為false時(shí)線程就會(huì)被阻塞,直到該布爾值為true時(shí)線程才被釋放。操縱這個(gè)布爾值的方法有set()和reset(),前者是把布爾值設(shè)成true;后者則設(shè)成false。這相當(dāng)于一個(gè)開(kāi)關(guān),調(diào)用了reset之后線程執(zhí)行到wait就暫停了,直到set才恢復(fù)。它有兩個(gè)子類(lèi),使用的方式類(lèi)似,區(qū)別在于autoresetevent調(diào)用set之后自動(dòng)調(diào)用reset,使得開(kāi)關(guān)馬上恢復(fù)關(guān)閉狀態(tài);而manualresetevent就需要手動(dòng)調(diào)用set讓開(kāi)關(guān)關(guān)閉。這樣就達(dá)到一個(gè)效果一般情況下autoresetevent每次釋放的時(shí)候能讓一條線程通過(guò);而manualresetevent在手動(dòng)調(diào)用reset之前有可能會(huì)讓多條線程通過(guò)。
semaphore的內(nèi)部是維護(hù)著一個(gè)整形,當(dāng)構(gòu)造一個(gè)semaphore對(duì)象時(shí)會(huì)指定最大的信號(hào)量與初始信號(hào)量值,每當(dāng)調(diào)用一次waitone,信號(hào)量就會(huì)加1,當(dāng)加到最大值時(shí),線程就會(huì)被阻塞,當(dāng)調(diào)用release的時(shí)候就會(huì)釋放一個(gè)或多個(gè)信號(hào)量,此時(shí)被阻塞掉的一個(gè)或多個(gè)線程就會(huì)被釋放。這個(gè)就符合生產(chǎn)者與消費(fèi)者問(wèn)題了,當(dāng)生產(chǎn)者不斷往產(chǎn)品隊(duì)列中加入產(chǎn)品時(shí),他就會(huì)waitone,當(dāng)隊(duì)列滿(mǎn)了,就相當(dāng)于信號(hào)量滿(mǎn)了,生成者就會(huì)被阻塞,當(dāng)消費(fèi)者消費(fèi)掉一個(gè)商品時(shí),就會(huì)release釋放掉產(chǎn)品隊(duì)列中的一個(gè)空間,此時(shí)因沒(méi)有空間存放產(chǎn)品的生產(chǎn)者又可以開(kāi)始工作往產(chǎn)品隊(duì)列中存放產(chǎn)品了。
mutex的內(nèi)部與規(guī)則相對(duì)前面兩者稍微復(fù)雜一點(diǎn),先說(shuō)與前面相似的地方就是同樣都會(huì)通過(guò)waitone來(lái)阻塞當(dāng)前線程,通過(guò)releastmutex來(lái)釋放對(duì)線程的阻塞。區(qū)別在于waitone的允許第一個(gè)調(diào)用的線程通過(guò),其余后面的線程調(diào)用到waitone就會(huì)被阻塞,通過(guò)了waitone的線程可以重復(fù)調(diào)用waitone多次,但是必須調(diào)用同樣次數(shù)的releasemutex來(lái)釋放,否則會(huì)因?yàn)榇螖?shù)不對(duì)等導(dǎo)致別的線程一直處于阻塞的狀態(tài)。相比起之前的幾個(gè)構(gòu)造,這個(gè)構(gòu)造會(huì)有線程所有權(quán)與遞歸這兩個(gè)概念,這個(gè)是單純靠前面的構(gòu)造都無(wú)法實(shí)現(xiàn)的,額外封裝除外。
混合構(gòu)造
上面的基元構(gòu)造是用了最簡(jiǎn)單的實(shí)現(xiàn)方式,用戶(hù) 模式有用戶(hù)模式的快,但是它會(huì)帶來(lái)cpu時(shí)間的浪費(fèi);內(nèi)核模式解決了這個(gè)問(wèn)題,但是會(huì)帶來(lái)性能上的損失,各有利弊,而混合構(gòu)造則是集合了兩者的利,它會(huì)在內(nèi)部通過(guò)一定策略適當(dāng)?shù)臅r(shí)機(jī)使用用戶(hù)模式,再另一種情況下又會(huì)使用內(nèi)核模式。但是這些層層判斷帶來(lái)的是內(nèi)存上的開(kāi)銷(xiāo)。在多線程同步中沒(méi)有完美的構(gòu)造,各個(gè)構(gòu)造都有利弊,存在即有意義,結(jié)合具體的應(yīng)用場(chǎng)景就會(huì)有最優(yōu)的構(gòu)造可供使用。只是在于我們能否按照具體的場(chǎng)景權(quán)衡利弊而已。
各種slim后綴的類(lèi),在system.threading命名空間中,可以看到若干個(gè)以slim后綴結(jié)尾的類(lèi):manualreseteventslim,semaphoreslim,readerwriterlockslim。除了最后一個(gè),其余兩個(gè)都是在基元內(nèi)核模式中有一樣的構(gòu)造,但是這三個(gè)類(lèi)都是原有構(gòu)造的簡(jiǎn)化版,尤其是前兩個(gè),使用方式跟原有的一樣,但是盡量避免使用操作系統(tǒng)的內(nèi)核對(duì)象,而達(dá)到了輕量級(jí)的效果。比如在semaphoreslim中使用了內(nèi)核構(gòu)造manualresetevent,但是這個(gè)構(gòu)造是通過(guò)延時(shí)初始化,沒(méi)達(dá)到非不得已時(shí)都不使用。至于readerwriterlockslim則在后面再介紹。
monitor與lock,lock關(guān)鍵字可謂是最廣為人知的一種實(shí)現(xiàn)多線程同步的手段,那么下面則又從一段代碼說(shuō)起
這個(gè)方法相當(dāng)簡(jiǎn)單且無(wú)實(shí)際意義,它只是為了看編譯器把這段代碼編譯成什么樣子,通過(guò)查看il如下
留意到il代碼中出現(xiàn)了try…finally語(yǔ)句塊、monitor.enter與monotor.exit方法。然后把代碼更改一下再編譯看看il
il代碼
代碼比較相似,但并非等價(jià),實(shí)際上與lock語(yǔ)句塊等價(jià)的代碼如下
那么既然lock本質(zhì)上是調(diào)用了monitor,那monitor是如何通過(guò)對(duì)一個(gè)對(duì)象加鎖,然后實(shí)現(xiàn)線程同步。原來(lái)每個(gè)在托管堆里面的對(duì)象都有兩個(gè)固定的成員,一個(gè)指向該對(duì)象類(lèi)型的指針,另一個(gè)是指向一個(gè)線程同步塊索引。這個(gè)索引指向一個(gè)同步塊數(shù)組的元素,monitor對(duì)線程加鎖就是靠這個(gè)同步塊。按照jeffrey(clr via c#的作者)的說(shuō)法同步塊中有三個(gè)字段,所有權(quán)的線程id,等待線程的數(shù)量,遞歸的次數(shù)。然而我通過(guò)另一批文章了解到線程同步塊的成員并非單純這幾個(gè),有興趣的同學(xué)可以去閱讀《揭示同步塊索引》的文章,有兩篇。 當(dāng)monitor需要為某個(gè)對(duì)象obj加鎖時(shí),它會(huì)檢查obj的同步塊索引有否為數(shù)組的某個(gè)索引,如果是-1的,則從數(shù)組中找出一個(gè)空閑的同步塊與之關(guān)聯(lián),同時(shí)同步塊的所有權(quán)線程id就記錄下當(dāng)前線程的id;當(dāng)再次有線程調(diào)用monitor的時(shí)候就會(huì)檢查同步塊的所有權(quán)id和當(dāng)前線程id是否對(duì)應(yīng)上,能對(duì)應(yīng)上的就讓其通過(guò),在遞歸次數(shù)上加1,如果對(duì)應(yīng)不上的就把該線程扔到一個(gè)就緒隊(duì)列(這個(gè)隊(duì)列實(shí)際上也是存在同步塊里面)中,并將其阻塞;這個(gè)同步塊會(huì)在調(diào)用exit的時(shí)候檢查遞歸次數(shù)確保遞歸完了就清除所有權(quán)線程id。通過(guò)等待線程數(shù)量得知是否有線程在等待,如果有則從等待隊(duì)列中取出線程并釋放,否則就解除與同步塊的關(guān)聯(lián),讓同步塊等待被下個(gè)被加鎖的對(duì)象使用。
monitor中還有一對(duì)方法wait與pulse。前者可以使得獲得到鎖的線程短暫地將鎖釋放,而當(dāng)前線程就會(huì)被阻塞而放入等待隊(duì)列中。直到其他線程調(diào)用了pulse方法,才會(huì)從等待隊(duì)列中把線程放到就緒隊(duì)列中,等待下次鎖被釋放時(shí),才有機(jī)會(huì)被再次獲取鎖,具體能否獲取就要看等待隊(duì)列中的情況了。
readerwriterlock讀寫(xiě)鎖,傳統(tǒng)的lock關(guān)鍵字(即等價(jià)于monitor的enter和exit),他對(duì)共享資源的鎖是全互斥鎖,一經(jīng)加鎖的資源其他資源完全不能訪問(wèn)。
而readerwriterlock對(duì)互斥資源的加的鎖分讀鎖與寫(xiě)鎖,類(lèi)似于數(shù)據(jù)庫(kù)中提到的共享鎖和排他鎖。大致情況是加了讀鎖的資源允許多個(gè)線程對(duì)其訪問(wèn),而加了寫(xiě)鎖的資源只有一個(gè)線程可以對(duì)其訪問(wèn)。兩種加了不同縮的線程都不能同時(shí)訪問(wèn)資源,而嚴(yán)格來(lái)說(shuō),加了讀鎖的線程只要在同一個(gè)隊(duì)列中的都能訪問(wèn)資源,而不同隊(duì)列的則不能訪問(wèn);加了寫(xiě)鎖的資源只能在一個(gè)隊(duì)列中,而寫(xiě)鎖隊(duì)列中只有一個(gè)線程能訪問(wèn)資源。區(qū)分讀鎖的線程是否在于統(tǒng)一個(gè)隊(duì)列中的判斷標(biāo)準(zhǔn)是,本次加讀鎖的線程與上次加讀鎖的線程這個(gè)時(shí)間段中,有否別的線程加了寫(xiě)鎖,沒(méi)沒(méi)別的線程加寫(xiě)鎖,則這兩個(gè)線程都在同一個(gè)讀鎖隊(duì)列中。
readerwriterlockslim和readerwriterlock類(lèi)似,是后者的升級(jí)版,出現(xiàn)在.net framework3.5,據(jù)說(shuō)是優(yōu)化了遞歸和簡(jiǎn)化了操作。在此遞歸策略我尚未深究過(guò)。目前大概列舉一下它們通常用的方法
readerwriterlock常用的方法
acqurie或release readerlock或writelock 的排列組合
upgradetowritelock/downgradefromwritelock 用于在讀鎖中升級(jí)到寫(xiě)鎖。當(dāng)然在這個(gè)升級(jí)的過(guò)程中也涉及到線程從讀鎖隊(duì)列切換到寫(xiě)鎖隊(duì)列中,因此需要等待。
releaselock/restorelock 釋放所有鎖和恢復(fù)鎖狀態(tài)
readerwriterlock實(shí)現(xiàn)idispose接口,其方法則是以下模式
tryenter/enter/exit readlock/writelock/upgradeablereadlock
(以上內(nèi)容引用自另一篇筆記《readerwriterlock》)
coutdownevent比較少用的混合構(gòu)造,這個(gè)跟semaphore相反,體現(xiàn)在semaphore是在內(nèi)部計(jì)數(shù)(也就是信號(hào)量)達(dá)到最大值的時(shí)候讓線程阻塞,而countdownevent是在內(nèi)部計(jì)數(shù)達(dá)到0的時(shí)候才讓線程阻塞。其方法有
1
2
3
4
|
addcount //計(jì)數(shù)遞增; signal //計(jì)數(shù)遞減; reset //計(jì)數(shù)重設(shè)為指定或初始; wait //當(dāng)且僅當(dāng)計(jì)數(shù)為0才不阻塞,否則就阻塞。 |
barrier也是一個(gè)比較少用的混合構(gòu)造,用于處理多線程在分步驟的操作中協(xié)作問(wèn)題。它內(nèi)部維護(hù)著一個(gè)計(jì)數(shù),該計(jì)數(shù)代表這次協(xié)作的參與者數(shù)量,當(dāng)不同的線程調(diào)用signalandwait的時(shí)候會(huì)給這個(gè)計(jì)數(shù)加1并且把調(diào)用的線程阻塞,直到計(jì)數(shù)達(dá)到最大值的時(shí)候,才會(huì)釋放所有被阻塞的線程。假設(shè)還是不明白的話(huà)就看一下msnd上面的示例代碼
這里給barrier初始化的參與者數(shù)量是3,同時(shí)每完成一個(gè)步驟的時(shí)候會(huì)調(diào)用委托,該方法是輸出count的值步驟索引。參與者數(shù)量后來(lái)增加了兩個(gè)又減少了一個(gè)。每個(gè)參與者的操作都是相同,給count進(jìn)行原子自增,自增完則調(diào)用sgnalandwait告知barrier當(dāng)前步驟已完成并等待下一個(gè)步驟的開(kāi)始。但是第三次由于回調(diào)方法里拋出了一個(gè)異常,每個(gè)參與者在調(diào)用signalandwait的時(shí)候都會(huì)拋出一個(gè)異常。通過(guò)parallel開(kāi)始了一個(gè)并行操作。假設(shè)并行開(kāi)的作業(yè)數(shù)跟barrier參與者數(shù)量不一樣就會(huì)導(dǎo)致在signalandwait會(huì)有非預(yù)期的情況出現(xiàn)。
接下來(lái)說(shuō)兩個(gè)attribute,這個(gè)估計(jì)不算是同步構(gòu)造,但是也能在線程同步中發(fā)揮作用
methodimplattribute這個(gè)attribute適用于方法的,當(dāng)給定的參數(shù)是methodimploptions.synchronized,它會(huì)對(duì)整個(gè)方法的方法體進(jìn)行加鎖,凡是調(diào)用這個(gè)方法的線程在沒(méi)有獲得鎖的時(shí)候就會(huì)被阻塞,直到擁有鎖的線程釋放了才將其喚醒。對(duì)靜態(tài)方法而言它就相當(dāng)于把該類(lèi)的類(lèi)型對(duì)象給鎖了,即lock(typeof(classtype));對(duì)于實(shí)例方法他就相當(dāng)于把該對(duì)象的實(shí)例給鎖了,即lock(this)。最開(kāi)始對(duì)它內(nèi)部調(diào)用了lock這個(gè)結(jié)論存在猜疑,于是用il編譯了一下,發(fā)現(xiàn)方法體的代碼沒(méi)啥異樣,查看了一些源碼也好無(wú)頭緒,后來(lái)發(fā)現(xiàn)它的il方法頭跟普通的方法有區(qū)別,多了一個(gè)synchronized
于是網(wǎng)上找各種資料,最后發(fā)現(xiàn)"junchu25"的博客[1][2]里提到用windbg來(lái)查看jit生成的代碼。
調(diào)用attribute的
調(diào)用lock的
對(duì)于用這個(gè)attribute實(shí)現(xiàn)的線程同步連jeffrey都不推薦使用。
system.runtime.remoting.contexts.synchronizationattribute這個(gè)attribute適用于類(lèi),在類(lèi)的定義中加了這個(gè)attribute并繼承與contextboundoject的類(lèi),它會(huì)對(duì)類(lèi)中的所有方法都加上同一個(gè)鎖,對(duì)比methodimplattribute它的范圍更廣,當(dāng)一個(gè)線程調(diào)用此類(lèi)的任何方法時(shí),如果沒(méi)有獲得鎖,那么該線程就會(huì)被阻塞。有個(gè)說(shuō)法是它本質(zhì)上調(diào)用了lock,對(duì)于這個(gè)說(shuō)法的求證就更不容易,國(guó)內(nèi)的資源少之又少,里面又涉及到appdomain,線程上下文,最后核心的就是由synchronizedservercontextsink這個(gè)類(lèi)去實(shí)現(xiàn)的。appdomain應(yīng)該要另立篇進(jìn)行介紹。但是在這里也要稍微說(shuō)一下,以前以為內(nèi)存中就是有線程棧與堆內(nèi)存,而這只是很基本的劃分,堆內(nèi)存還會(huì)劃分成若干個(gè)appdomain,在每個(gè)appdomain中也至少有一個(gè)上下文,每個(gè)對(duì)象都會(huì)從屬與一個(gè)appdomain里面的一個(gè)上下文中??鏰ppdomain的對(duì)象是不能直接訪問(wèn)的,要么進(jìn)行按值封送(相當(dāng)于深復(fù)制一個(gè)對(duì)象到調(diào)用的appdomain),要么就按引用封送。對(duì)于按引用封送則需要該類(lèi)繼承marshalbyrefobject。對(duì)繼承了這個(gè)類(lèi)的對(duì)象進(jìn)行調(diào)用時(shí)都不是調(diào)用類(lèi)的本身,而是通過(guò)代理的形式進(jìn)行調(diào)用。那么跨上下文的也需要進(jìn)行按值封送操作。平常構(gòu)造的一個(gè)對(duì)象都是在進(jìn)程默認(rèn)appdomain下的默認(rèn)上下文中,而使用了synchronizationattribute特性的類(lèi)它的實(shí)例是屬于另外的一個(gè)上下文中,繼承了contextboundobject基類(lèi)的類(lèi)進(jìn)行跨上下文訪問(wèn)對(duì)象時(shí)也是通過(guò)按引用封送的方式用代理訪問(wèn)對(duì)象,并非訪問(wèn)到對(duì)象本身。至于是否跨上下文訪問(wèn)對(duì)象可以通過(guò)的remotingservices.isobjectoutofcontext(obj)方法進(jìn)行判斷。synchronizedservercontextsink是mscorlib的一個(gè)內(nèi)部類(lèi)。當(dāng)線程調(diào)用跨上下文的對(duì)象時(shí),這個(gè)調(diào)用會(huì)被synchronizedservercontextsink封裝成workitem的對(duì)象,該對(duì)象也mscorlib的中的一個(gè)內(nèi)部類(lèi),synchronizedservercontextsink就請(qǐng)求synchronizationattribute,attribute根據(jù)現(xiàn)在是否有多個(gè)workitem的執(zhí)行請(qǐng)求來(lái)決定當(dāng)前處理的這個(gè)workitem會(huì)馬上執(zhí)行還是放到一個(gè)先進(jìn)先出的workitem隊(duì)列中按順序執(zhí)行,這個(gè)隊(duì)列是synchronizationattribute的一個(gè)成員,隊(duì)列成員入隊(duì)出隊(duì)時(shí)或者attribute判斷是否馬上執(zhí)行workitem時(shí)都需要獲取一個(gè)lock的鎖,被鎖的對(duì)象也正是這個(gè)workitem的隊(duì)列。這里面涉及到幾個(gè)類(lèi)的交互,鄙人現(xiàn)在還沒(méi)完全看清,以上這個(gè)處理過(guò)程可能有錯(cuò),待分析清楚再進(jìn)行補(bǔ)充。不過(guò)通過(guò)這個(gè)attribute實(shí)現(xiàn)的線程同步按逼人的直覺(jué)也是不推薦使用的,主要是性能方面的損耗,鎖的范圍也比較大。
以上就是本文的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,同時(shí)也希望多多支持服務(wù)器之家!
原文鏈接:http://www.cnblogs.com/HopeGi/p/6306076.html