




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
先拋開你所熟知的信號量、鎖、同步原語等技術(shù),思考這個問題:如何保證并發(fā)讀寫的準確性?一個沒有任何并發(fā)編程經(jīng)驗的程序員可能會覺得很簡單:這有什么問題呢,同時讀寫能有什么問題,最多就是讀到過期的數(shù)據(jù)而已。一個理想的世界當(dāng)然是這樣,只可惜實際上的機器世界往往隱藏了很多不容易被察覺的事情。至少有兩個行為會影響這個結(jié)論:?編譯器往往有指令重排序的優(yōu)化;例如程序員看到的源代碼是"=3;b=4;,而實際上執(zhí)行的順序可能是b=4;a=3:,這是因為編譯器為了優(yōu)化執(zhí)行效率可能對指令進行重排序;?高級編程語言所支持的運算往往不是原子化的;例如a+=3實際上包含了讀變量、加運算和寫變量三次原子操作。既然整個過程并不是原子化的,就意味著隨時有其它“入侵者”侵入修改數(shù)據(jù)。更為隱藏的例子:對于變量的讀寫甚至可能都不是原子化的。不同機器讀寫變量的過程可能是不同的,有些機器可能是64位數(shù)據(jù)一次性讀寫,而有些機器是32位數(shù)據(jù)一次讀寫。這就意味著一個64位的數(shù)據(jù)在后者的讀寫上實際上是分成兩次完成的!試想,如果你試圖讀取一個64位數(shù)據(jù)的值,先讀取了低32的數(shù)據(jù),這時另一個線程切進來修改了整個數(shù)據(jù)的值,最后你再讀取高32的值,將高32和低32的數(shù)據(jù)拼成完整的值,很明顯會得到一個預(yù)期以外的數(shù)據(jù)??雌饋?,整個并發(fā)編程的世界里一切都是不確定的,我們不知道每次讀取的變量到底是不是及時、準確的數(shù)據(jù)。幸運的是,很多語言都有一個kappervs-before的規(guī)則,能幫助我們在不確定的并發(fā)世界里尋找一絲確定性。happens-before你可以把八s-before看作一種特殊的比較運算,就好像)、<一樣。對應(yīng)的,還有happen-after,它們之間的關(guān)系也好像〉、<一樣:如果ahappens-beforeb,那么bhappens-aftera那是否存在既不滿足ahappens-beforeb,也不滿足bhappens-beforea的情況呢,就好像既不滿足a>b,也不滿足人。(意味著匕==〃)?當(dāng)然是肯定的,這種情況稱為:a和bhappenconcurrent也,也就是同時發(fā)生,這就回到我們之前所熟知的世界里了。happ^-before有什么用呢?它可以用來幫助我們厘清兩個并發(fā)讀寫之間的關(guān)系。對于并發(fā)讀寫問題,我們最關(guān)心的經(jīng)常是reader是否能準確觀察到writer寫入的值。正是為這個問題設(shè)計的,具體來說,要想讓某次讀取r準確觀察到某次寫入W,只需滿足:.Whappens-beforer;.對變量的其它寫入wl,要么wlhappens-beforeW,要么Xhappens-beforewl;簡單理解就是沒有其它寫入覆蓋這次寫入;只要滿足這兩個條件,那我們就可以自信地肯定我們一定能讀取到正確的值。一個新的問題隨之誕生:那如何判斷akappe八s-beforeb是否成立呢?你可以類比思考數(shù)學(xué)里如何判斷”>b是否成立的過程,我們的做法很簡單:.基于一些簡單的公理;例如自然數(shù)的自然大?。?>2>工.基于比較運算符的傳遞性,也就是如果且b>c,則a>c判斷ahappens-beforeb的過程也是類似的:根據(jù)一些簡單的明確的happens-More關(guān)系,再結(jié)合kappens-before的傳遞性,推導(dǎo)出我們所關(guān)心的W和r之間的happens-before關(guān)系。happe八s-before傳遞性:如果ahappen-beforeb,且bhappens-beforeC,貝I」ahappen-beforeC因此我們只需要了解這些明確的kapp^-before關(guān)系,就能在并發(fā)世界里尋找到寶貴的確定性了。go語言中的happens-before關(guān)系具體的happens-before關(guān)系是因語言而異的,這里只介紹go語言相關(guān)的規(guī)則,感興趣可以直接閱讀官方文檔,有更完整、準確的說明。自然執(zhí)行首先,最簡單也是最直觀的happens-before規(guī)則:在同一個goroutine里,書寫在前的代碼happens-before書寫在后的代碼。例如:?=3;//(工)b=4;//(2)貝!JQ)人即pe八s-beFo%(2)。我們上面提到指令重排序,也就是實際執(zhí)行的順序與書寫的順序可能不一致,但happens-before與指令重排序并不矛盾,即使可能發(fā)生指令重排序,我們依然可以說初始化每個g。文件都可以有一個郵t方法,用于執(zhí)行某些初始化邏輯。當(dāng)我們開始執(zhí)行某個出小次方法時,go會先在一個goroutine里做初始化工作,也就是執(zhí)行所有g(shù)o文件的Mit方法,這個過程中g(shù)o可能創(chuàng)建多個goroutine并發(fā)地執(zhí)行,因此通常情況卜各個認證方法是沒有happe八s-before關(guān)系的。關(guān)于,八讓方法有兩條happens-before規(guī)則:.a包導(dǎo)入了b包,此時b包的iKit方法happens-beforea包的所有代碼;.所有init方法kappeias-beforekv\aik\方法;goroutinegoroutine相關(guān)的規(guī)則主要是其創(chuàng)建和銷毀的:.goroutine的創(chuàng)建happens-before其執(zhí)行;.goroutine的完成不保證happens-before任何代碼;第一條規(guī)則舉個簡單的例子即可:varastHcgfuiacf()(fkv\t.Println(a)//(1)}funchello(){a="Mlojworld11//(2)9。FO〃⑶)因為goroutine的創(chuàng)建happens-before.其執(zhí)行,所以⑶happens-before(1),又因為自然執(zhí)行的規(guī)則(2)happen-before(3),根據(jù)傳遞性,所以⑵happen-before⑴,這樣保證了我們每次打印出來的都是“hell。world”而不是空字符串。第二條規(guī)則是少見的否定句式,同樣舉個簡單的例子:varastringfuiachello(){goft(八of){a~"hello"K)//(1)fkv\t.Printlk\(a)//(2)}由于goroutine的完成不保證happens-before任何代碼,因此⑴happens-before(2)不成立,這樣我們就不能保證每次打印的結(jié)果都是"hell。"。通道通道channel是go語言中用于goroutine之間通信的主要渠道,因此理解通道之間的happens-before規(guī)則也至關(guān)重要。.對于緩沖通道,向通道發(fā)送數(shù)據(jù)happen-before從通道接收到數(shù)據(jù)結(jié)合一個例子:varc=kv\ake(chanMt,astringfimcfO(a=''Mio,world”//(1)c<-O//(2))func出川八0(go-〃⑶<-C//(4)fkv\t.Pnntlia(a)//(5))c是一個緩沖通道,因此向通道發(fā)送數(shù)據(jù)。叩e八s-before從通道接收到數(shù)據(jù),也就是⑵happens-More(4),再結(jié)合自然執(zhí)行規(guī)則以及傳遞性不難推導(dǎo)出(1)happens-before(5),也就是打印的結(jié)果保證是"helloworld"。有趣的是,如果我們把C的定義改為varc-wakefcMan認t)也就是無緩沖通道,上面的結(jié)論就不存在了(注D,打印的結(jié)果不一定為“hell。world”,這是因為:.對于無緩沖通道,從通道接收數(shù)據(jù)happe^-before向通道發(fā)送數(shù)據(jù)我們可以將上述例子稍微調(diào)整下:vavc= nint)varastringfemeFO(a="hello,wo”d"http://(1)<-C//(2))fuM打\浦八0{go-〃⑶C<-W//(4)fkv\t.Pnnt/n(?)//⑸對于無緩沖通道,(2)happens-before(4),再根據(jù)傳遞性,⑴happens-before⑸,因此依然可以保證打印的結(jié)果是“helloworld"o可以這么理解這兩者的差異,緩沖通道的目的是緩沖發(fā)送方發(fā)送的數(shù)據(jù),這就意味著發(fā)送方很可能先發(fā)送數(shù)據(jù),過一段時間后接收方才接收,或者發(fā)送方發(fā)送的速度超過接收方接收的速度,因此緩沖通道的發(fā)送happen-before接收就自然而然了;相反,非緩沖通道是沒有緩沖區(qū)的,先發(fā)起的發(fā)送方和接收方都會阻塞至另一方準備好,如果我們使用了非緩沖通道,則意味著我們認為我們的場景下接收發(fā)生在發(fā)送之前,否則我們就會使用緩沖通道了,因此非緩沖通道的接收happens-before發(fā)送。.對于緩沖通道,第k次接收happens-before.第k+C次發(fā)送,C是緩沖通道的容量這條規(guī)則是緩沖通道的通用規(guī)則(有趣的是,上面針對非緩沖通道的第2條規(guī)則也可以看成這個規(guī)則的特例:C取0)。這個規(guī)則看起來復(fù)雜,我們看個例子就清晰了:var 〃八Mt,3)func(//work是一個worker列表,其中的元素w都是可執(zhí)行函數(shù)forw:=ravagework{gofunc(wfutacQ){li^it<-1//(1)w()〃(2)//⑶Kw))select^我們先套用一下上面的規(guī)則,則:"第1次(3)人華pe八s-before第4次(1)"、〃第2次(3)kappcns-before第5次⑴"、"第3次(3)k〃ppens-before第6次(1)" ,再結(jié)合傳遞性:"第1次(2)k〃ppens-Sefo/e第1次(3)happens-%F"e第4次ns-befo/e第4次(2)"、"第2次(2)kappens-before第2次⑶k〃叩爾-before第5次(l)h?ppens-before第5次(2)”,簡單地說:"第1次(2)h〃叩e八s-before第4次⑵"、“第2次(2)h〃ppens-Sefore第5次(2)"、"第3次(2)happens4eF"e第6次(2)”這樣我們雖然沒有做任何分批,卻事實上將workers分成三個一批、每批并發(fā)地執(zhí)行。這就是通過這條happens-before規(guī)則保證的。這個規(guī)則理解起來其實也很簡單,C是通道的容量,如果無法保證第k次接收happen-before第k+C次發(fā)送,那通道的緩沖就不夠用了。注L以上是官方文檔給的規(guī)則和例子,但是筆者在嘗試將第一個例子的c改成無緩沖通道后發(fā)現(xiàn)每次打印的依然穩(wěn)定是“hell。world",并沒有出現(xiàn)預(yù)期的空字符串,也就是看起來〃華.八s4ef”e規(guī)則依然成立。但既然官方文檔說無法保證,那我們開發(fā)時還是按照happen-before不成立比較好。鎖鎖也是并發(fā)編程里非常常用的一個數(shù)據(jù)結(jié)構(gòu)。go語言中支持的鎖主要有兩種:sgnc.Mutex和sgnc.RWMutex,即普通鎖和讀寫鎖(讀寫鎖的原理可以參見另一篇文章)。普通鎖的happen-before.規(guī)則也很直觀:.對鎖實例調(diào)用h次Unlockhappen-before調(diào)用Lockha次,只要八<kv\請看這個例子:varIsync.MutexvarastringfuncfQ(a="hello,world"http://(1).UnlockQ//(2))furc{I.LockO//⑶goFO//(4)I.LockO〃⑸pn^t(a)//(6))上面調(diào)用了Unlock一次,Lock兩次,因此⑵Kappe八s-beFo%(5),從而(1)happens-before(6)而讀寫鎖的規(guī)則為:2.對讀寫鎖實例的某一次Unlock調(diào)用,happens-after的RLock調(diào)用對應(yīng)的RUialock調(diào)用happens-before下一次Lock調(diào)用。其實本質(zhì)就是讀寫鎖的原理:讀寫互斥,簡單地理解就是寫鎖釋放后先獲取了讀鎖,則讀鎖的釋放會以「pens-before下一次寫鎖的獲取。注意上面的規(guī)則是"存在",而不是"任意"Oncesync中還提供了一個。八%的數(shù)據(jù)結(jié)構(gòu),用于控制并發(fā)編程中只執(zhí)行一次的邏輯,例如:vavastriiagvaronce-sy八c.。八cefuMsetupO{a="hell。,world"Fkwt.P片八七伍("setup")]fuMdopriv\t(){0八ce.Do(s血p)fkv\t.Printlk\(a)]fuMtwopnntO(godopri^tOgodopHnt。)會打印"hello,world”兩次和“setup”一次。。八ce的happen-before規(guī)則也很直觀:第一次執(zhí)行。八"Q。happen-before其余的。八“Q。應(yīng)用掌握了上述的基本ha叩跑S-before規(guī)則,可以結(jié)合起來分析更復(fù)雜的場景了,來看這個例子:varbintfuncfif)(a-X//(1)b=2〃(2))funcgO(pnntfi?)//⑶print(d)//(4)1fuMMW八0(gofogO}這里⑴happens-before(2),⑶kappens-before(4),但是⑴與⑶、(4)之間以及(2)與(3)、(4)之間并沒有次華pens-before關(guān)系,這時候結(jié)果是不確定的,一種有趣的結(jié)果是2、0,也就是(1)、(2)之間發(fā)生了指令重排序?,F(xiàn)在讓我們修改一下上面的代碼,讓它按我們預(yù)期的邏輯運行:要么打印0、0,要么打印1、2o使用鎖vara,biiatvarlocksg八c.MutexfuMf(){lock.LockO//(X)a=i//(2)b-Z//⑶lock.UialockQ//(4)}ftmcg(){lock.LockQ//⑸pri
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 廉潔過節(jié)試題及答案
- 三基訓(xùn)練試題及答案
- 初級中醫(yī)考試題及答案
- 天價彩禮面試題及答案
- 醫(yī)師招考試題及答案
- 產(chǎn)品知識試題及答案
- 鐵路實訓(xùn)考試試題及答案
- 語文小升初考試題及答案
- 鋼琴七級試題及答案
- 基層自治面試題及答案
- GB/T 603-2002化學(xué)試劑試驗方法中所用制劑及制品的制備
- GB/T 1040.3-2006塑料拉伸性能的測定第3部分:薄膜和薄片的試驗條件
- 第37次全國計算機等級考試考務(wù)培訓(xùn)-課件
- 做好迎接CNAS現(xiàn)場評審工作的培訓(xùn)課件
- 完整的舊路改造施工程施工方案設(shè)計
- 新生入學(xué)登記表新生入學(xué)情況表word模版
- CorelDRAW-X4案例教程上電子教案課件
- 中藥熏洗法操作評分標(biāo)準與流程
- 光伏發(fā)電項目監(jiān)理工作制度
- 邊坡防護支護動態(tài)設(shè)計信息化施工管理措施
- s鐵路預(yù)應(yīng)力混凝土連續(xù)梁(鋼構(gòu))懸臂澆筑施工技術(shù)指南
評論
0/150
提交評論