go語言happens-before原則及應(yīng)用_第1頁
go語言happens-before原則及應(yīng)用_第2頁
go語言happens-before原則及應(yīng)用_第3頁
go語言happens-before原則及應(yīng)用_第4頁
go語言happens-before原則及應(yīng)用_第5頁
已閱讀5頁,還剩9頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

引言

先拋開你所熟知的信號(hào)量、鎖、同步原語等技術(shù),思考這個(gè)問題:如何保證并發(fā)

讀寫的準(zhǔn)確性?一個(gè)沒有任何并發(fā)編程經(jīng)驗(yàn)的程序員可能會(huì)覺得很簡單:這有什

么問題呢,同時(shí)讀寫能有什么問題,最多就是讀到過期的數(shù)據(jù)而已。一個(gè)理想的

世界當(dāng)然是這樣,只可惜實(shí)際上的機(jī)器世界往往隱藏了很多不容易被察覺的事情。

至少有兩個(gè)行為會(huì)影響這個(gè)結(jié)論:

?編譯器往往有指令重排序的優(yōu)化;例如程序員看到的源代碼是以4;,

而實(shí)際上執(zhí)行的順序可能是以4;好3;,這是因?yàn)榫幾g器為了優(yōu)化執(zhí)行效

率可能對(duì)指令進(jìn)行重排序;

?高級(jí)編程語言所支持的運(yùn)算往往不是原子化的;例如n+=3實(shí)際上包含了

讀變量、加運(yùn)算和寫變量三次原子操作。既然整個(gè)過程并不是原子化的,

就意味著隨時(shí)有其它“入侵者"侵入修改數(shù)據(jù)。更為隱藏的例子:對(duì)于變量

的讀寫甚至可能都不是原子化的。不同機(jī)器讀寫變量的過程可能是不同的,

有些機(jī)器可能是64位數(shù)據(jù)一次性讀寫,而有些機(jī)器是32位數(shù)據(jù)一次讀

寫。這就意味著一個(gè)64位的數(shù)據(jù)在后者的讀寫上實(shí)際上是分成兩次完成

的!試想,如果你試圖讀取一個(gè)64位數(shù)據(jù)的值,先讀取了低32的數(shù)據(jù),

這時(shí)另一個(gè)線程切進(jìn)來修改了整個(gè)數(shù)據(jù)的值,最后你再讀取高32的值,

將高32和低32的數(shù)據(jù)拼成完整的值,很明顯會(huì)得到一個(gè)預(yù)期以外的數(shù)

據(jù)。

看起來,整個(gè)并發(fā)編程的世界里一切都是不確定的,我們不知道每次讀取的變量

到底是不是及時(shí)、準(zhǔn)確的數(shù)據(jù)。幸運(yùn)的是,很多語言都有一個(gè)ha叩ens-before的

規(guī)則,能幫助我們?cè)诓淮_定的并發(fā)世界里尋找一絲確定性。

happens-before

你可以把ka叩c八s-before看作一種特殊的比較運(yùn)算,就好像>、<一樣。對(duì)應(yīng)的,

還有happen-after,它們之間的關(guān)系也好像〉、<一樣:

如果ahappens-beforeb,那么bhappens-aftera

那是否存在既不滿足ahappens-before-b,也不滿足bhappens-beforea的情況呢,

就好像既不滿足a泌,也不滿足分a(意味著以=")?當(dāng)然是肯定的,這種情況稱

為:a和bha叩enconcurrent/g,也就是同時(shí)發(fā)生,這就回到我們之前所熟知的世

界里了。

happen-before有什么用呢?它可以用來幫助我們厘清兩個(gè)并發(fā)讀寫之間的關(guān)系。

對(duì)于并發(fā)讀寫問題,我們最關(guān)心的經(jīng)常是reader是否能準(zhǔn)確觀察到writer寫入

的值。happens-before正是為這個(gè)問題設(shè)計(jì)的,具體來說,要想讓某次讀取r準(zhǔn)確

觀察到某次寫入W,只需滿足:

1.Whappcns-beforeC;

2.對(duì)變量的其它寫入wl,要么wlhappens-beforeW,要么Xhappens-beforewl;

簡單理解就是沒有其它寫入覆蓋這次寫入;

只要滿足這兩個(gè)條件,那我們就可以自信地肯定我們一定能讀取到正確的值。

一個(gè)新的問題隨之誕生:那如何判斷ah華pens4efo%b是否成立呢?你可以類比

思考數(shù)學(xué)里如何判斷a>b是否成立的過程,我們的做法很簡單:

1.基于一些簡單的公理;例如自然數(shù)的自然大?。?>2>工

2,基于比較運(yùn)算符的傳遞性,也就是如果a4且小c,則a>c

判斷ahappens-beforeb的過程也是類似的:根據(jù)一些簡單的明確的happens-before

關(guān)系,再結(jié)合happen-before的傳遞性,推導(dǎo)出我們所關(guān)心的W和r之間的

happens-before關(guān)系。

happens-before傳遞性:如果ahappen-beforeb,且bhappe八s-beforeC,

貝Uahappens-beforeC

因此我們只需要了解這些明確的kapp^-before關(guān)系,就能在并發(fā)世界里尋找到

寶貴的確定性了。

go語言中的happens-before關(guān)系

具體的happens-before關(guān)系是因語言而異的,這里只介紹go語言相關(guān)的規(guī)則,

感興趣可以直接閱讀官方文檔,有更完整、準(zhǔn)確的說明。

自然執(zhí)行

首先,最簡單也是最直觀的ha叩e八s-before規(guī)則:

在同一個(gè)goroutine里,書寫在前的代碼happens-before書寫在后

的代碼。

例如:

a=3;//(l,)b=4;//(2)

則(1)kappe八s-before(2)。我們上面提到指令重排序,也就是實(shí)際執(zhí)行的順序與書

寫的順序可能不一致,但h叩pens-before與指令重排序并不矛盾,即使可能發(fā)

生指令重排序,我們依然可以說(1)happens-before(2)。

初始化

每個(gè)g。文件都可以有一個(gè)屈t方法,用于執(zhí)行某些初始化邏輯。當(dāng)我們開始執(zhí)

行某個(gè)wam方法時(shí),go會(huì)先在一個(gè)goroutine里做初始化工作,也就是執(zhí)行所

有g(shù)o文件的以it方法,這個(gè)過程中g(shù)o可能創(chuàng)建多個(gè)goroutine并發(fā)地執(zhí)行,因

此通常情況卜各個(gè)init方法是沒有人即pens-before關(guān)系的。關(guān)于而t方法有兩條

happens-before規(guī)則:

l.a包導(dǎo)入了b包,此時(shí)b包的init方法happens-beforea包的所

有代碼;

2.所有iin.it方法happens-beforemam方法;

goroutine

goroutine相關(guān)的規(guī)則主要是其創(chuàng)建和銷毀的:

l.goroutine的創(chuàng)建happens-before其執(zhí)行;

2.goroutine的完成不保證happens-before任何代碼;

第一條規(guī)則舉個(gè)簡單的例子即可:

vavastring

fimcf(){

仇七Pn'n珈⑷//(1)

]

ftmckelloQ{

a="hdlo,world11//(2)

go侑//⑶

)

因?yàn)間oroutine的創(chuàng)建happens-before其執(zhí)行,所以⑶happen-before⑴,又因?yàn)?/p>

自然執(zhí)行的規(guī)則(2)happe八s-before⑶,根據(jù)傳遞性,所以⑵happen-before⑴,

這樣保證了我們每次打印出來的都是“hell。world”而不是空字符串。

第二條規(guī)則是少見的否定句式,同樣舉個(gè)簡單的例子:

varastrMg

fuMhello(){

gofuM()[a-"hello11K)//(1)

//(2)

}

由于goroutine的完成不保證kappeAS-before任何代碼,因此(1)happens-before

⑵不成立,這樣我們就不能保證每次打印的結(jié)果都是“hello"。

通道

通道channel是go語言中用于goroutine之間通信的主要渠道,因此理解通道

之間的happens-before規(guī)則也至關(guān)重要。

1.對(duì)于緩沖通道,向通道發(fā)送數(shù)據(jù)happe^-before從通道接收到數(shù)

據(jù)

結(jié)合一個(gè)例子:

varc=kv\ake(ckaniint,astH八g

fuHCf(){

a="hello,world11//(1)

c<-o//(2)

)

fmacnani八0(

go侑〃⑶

—C//(4)

fwt.Pnntfn(?)//(S)

)

c是一個(gè)緩沖通道,因此向通道發(fā)送數(shù)據(jù)ha叩ws-bef。%從通道接收到數(shù)據(jù),也就

是(2)ha叩ens-before(4),再結(jié)合自然執(zhí)行規(guī)則以及傳遞性不難推導(dǎo)出(1)

happens-before(5),也就是打印的結(jié)果保證是"helloworld"。

有趣的是,如果我們把C的定義改為varc=&nke(cha八認(rèn)t)也就是無緩沖通道,上

面的結(jié)論就不存在了(注1),打印的結(jié)果不一定為“helloworld”,這是因?yàn)椋?/p>

2.對(duì)于無緩沖通道,從通道接收數(shù)據(jù)happens-before向通道發(fā)送數(shù)

據(jù)

我們可以將上述例子稍微調(diào)整下:

varc=kv\ake(chaiaii^t)varastring

femeFO(

a="he。。,wo"d"http://(工)

<-C//(2)

)

fuMK/UU'八°{

g。侑〃⑶

C<-IO//C4)

fMt.Println(a)//(5)

)

對(duì)于無緩沖通道,(2)happens-before⑷,再根據(jù)傳遞性,(1)happens-before(5),

因此依然可以保證打印的結(jié)果是“helloworld”。

可以這么理解這兩者的差異,緩沖通道的目的是緩沖發(fā)送方發(fā)送的數(shù)據(jù),這就意

味著發(fā)送方很可能先發(fā)送數(shù)據(jù),過一段時(shí)間后接收方才接收,或者發(fā)送方發(fā)送的

速度超過接收方接收的速度,因此緩沖通道的發(fā)送ha眸He接收就自然而

然了;相反,非緩沖通道是沒有緩沖區(qū)的,先發(fā)起的發(fā)送方和接收方都會(huì)阻塞至

另一方準(zhǔn)備好,如果我們使用了非緩沖通道,則意味著我們認(rèn)為我們的場景下接

收發(fā)生在發(fā)送之前,否則我們就會(huì)使用緩沖通道了,因此非緩沖通道的接收

happens-before發(fā)送。

3.對(duì)于緩沖通道,第k次接收k即pens-況第k+C次發(fā)送,C是緩

沖通道的容量

這條規(guī)則是緩沖通道的通用規(guī)則(有趣的是,上面針對(duì)非緩沖通道的第2條規(guī)則

也可以看成這個(gè)規(guī)則的特例:C取0)。這個(gè)規(guī)則看起來復(fù)雜,我們看個(gè)例子就

清晰了:

varlihn.it=Make(ckanint,3)

fuin.C{

//work是一個(gè)worker列表,其中的元素w都是可執(zhí)行函數(shù)

Forw:=ravagework(

gofunc(wfunc0){

titbit<-1//(1)

w()〃(2)

//⑶

Kw)

)

select^}

}

我們先套用一下上面的規(guī)則,則:"第1次⑶h即pens-before第4次⑴"、"第2次

⑶happens-before第5次⑴"、"第3次⑶happens-before第6次Q)”,再結(jié)合傳

遞性:”第1次(2)happe八s-before第1次(3)kappe八s-before第4次(l)happens-before

第4次(2)"、"第2次(2)happens-before第2次⑶ha印ens-before第5次

(l)happen$-before第5次⑵”..,簡單地說:"第1^(2)happens-before第4次(2)"、

“第2次(2)happens-before第5次(2)"、"第3次⑵happens-before第6次(2)”..這樣

我們雖然沒有做任何分批,卻事實(shí)上將workers分成三個(gè)一批、每批并發(fā)地執(zhí)行。

這就是通過這條h叩pens-before規(guī)則保證的。

這個(gè)規(guī)則理解起來其實(shí)也很簡單,c是通道的容量,如果無法保證第k次接收

ha叩def-e第k+C次發(fā)送,那通道的緩沖就不夠用了。

注1:以上是官方文檔給的規(guī)則和例子,但是筆者在嘗試將第一個(gè)

例子的C改成無緩沖通道后發(fā)現(xiàn)每次打印的依然穩(wěn)定是"hello

world",并沒有出現(xiàn)預(yù)期的空字符串,也就是看起來happens-before

規(guī)則依然成立。但既然官方文檔說無法保證,那我們開發(fā)時(shí)還是按

照kappe^s-before不成立比較好。

鎖也是并發(fā)編程里非常常用的一個(gè)數(shù)據(jù)結(jié)構(gòu)。go語言中支持的鎖主要有兩種:

sy^.Mutex和sync.RWMutex,即普通鎖和讀寫鎖(讀寫鎖的原理可以參見另一篇文

章)。普通鎖的kappens-before規(guī)則也很直觀:

1.對(duì)鎖實(shí)例調(diào)用八次Unlockhappen-before調(diào)用Lock3次,只要八<

m

請(qǐng)看這個(gè)例子:

varIsyM.Mutex'/arastring

fuMf(){

a-"hello,world11//(工)

I.UialockQ//(2)

}

fuiaci^aii^O{

I.LockQ//⑶

gof()//(4)

I.LockQ//⑸

print(a)//(6)

}

上面調(diào)用了Unlock-'次,Lock兩次,011:k(2)happens-before(5),從而

(1)happen-before(6)

而讀寫鎖的規(guī)則為:

2.對(duì)讀寫鎖實(shí)例的某一■次Unlock調(diào)用,happen-after的RLock調(diào)用

對(duì)應(yīng)的RU八/ock調(diào)用happen-before下一1次Lock調(diào)用。

其實(shí)本質(zhì)就是讀寫鎖的原理:讀寫互斥,簡單地理解就是寫鎖釋放后先獲取了讀

鎖,則讀鎖的釋放會(huì)kappe^befo^下一次寫鎖的獲取。注意上面的規(guī)則是〃存在〃,

而不是〃任意〃。

Once

sync中還提供了一個(gè)Once的數(shù)據(jù)結(jié)構(gòu),用于控制并發(fā)編程中只執(zhí)行一次的邏輯,

例如:

vavastrii^gvarOMCsg八c.O八ce

femes血pO(

a="he"。,world"

fmt.PH八乜八("setup")

]

Femedoprii^tO{

0八ce.Do(s血p)

fkv\tPrikitlk\(a)

)

fuMtwoprilato{

go40PHzt()

godopH八t。

}

會(huì)打印"hello,world”兩次和“setup”一次。。八ce的kappas-before規(guī)則也很直觀:

第一次執(zhí)行。八cc.D。happens-before其余的Ov\ce.V)o

應(yīng)用

掌握了上述的基本人即2八s-bef"e規(guī)則,可以結(jié)合起來分析更復(fù)雜的場景了,來

看這個(gè)例子:

vava,bint

fuMf(){

4=1〃(1)

b=2//(2)

)

funcg()(

pni^t(b)//(3)

pnnt(a)//(4)

1

fuMm?m(){

goR)

g()

)

這里⑴ha叩e八s-before(2),⑶happens-6efore(4),但是(1)與⑶、⑷之間以及⑵與

(3)、(4)之間并沒有hnppe八s-bcfore關(guān)系,這時(shí)候結(jié)果是不確定的,一種有趣的結(jié)

果是2、0,也就是(1)、(2)之間發(fā)生了指令重排序?,F(xiàn)在讓我們修改一下上面的

代碼,讓它按我們預(yù)期的邏輯運(yùn)行:要么打印0、0,要么打印1、2o

使用鎖

vara,biiatvarlocksync-Mutex

fuMf(){

lock.Lock()//(工)

a=1//(2)

。=2//⑶

lock.UialockO//(4)

)

Femeg()(

lock.Lock()//(S)

prii^t(b)//(6)

pnnt(d)//(7)

lock.U^lockQ〃⑻

}

fuM{

3°2

go

)

回想下鎖的規(guī)則:

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論