




版權(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ì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年度直播平臺(tái)主播培訓(xùn)及管理合同
- 2025年度新能源汽車產(chǎn)業(yè)投資合作合同
- 二零二五年度商標(biāo)共營協(xié)議及跨國品牌合作合同
- 二零二五年度超市商品陳列與文化氛圍營造合同
- 2025年度民宿租賃合同終止及服務(wù)質(zhì)量協(xié)議
- 二零二五年度集體合同簽訂與新型學(xué)徒制實(shí)施
- 二零二五年度個(gè)人對(duì)個(gè)人科技成果轉(zhuǎn)化借款合同
- 2025年度機(jī)關(guān)炊事員食品安全培訓(xùn)聘用協(xié)議
- 日常行政管理事務(wù)處理指導(dǎo)書
- 日化用品行業(yè)供應(yīng)鏈優(yōu)化與市場拓展策略研究計(jì)劃
- 米伊林《十萬個(gè)為什么》導(dǎo)讀課課件
- 五年(2020-2024)高考?xì)v史真題分類匯編(山東)專題12 世界殖民體系的形成、瓦解與亞非拉民族民主運(yùn)動(dòng)(原卷版)
- 第六章-1八綱辨證
- 《中外城市建設(shè)史》考試復(fù)習(xí)題庫(附答案)
- 網(wǎng)絡(luò)平臺(tái)運(yùn)營合同三篇
- 《S品牌管理有限公司銷售人員績效考核問題及優(yōu)化建議(定量論文)》11000字
- 九年級(jí)語文下冊(cè) 第9課 魚我所欲也(分層作業(yè))(學(xué)生版)
- 2023年公務(wù)員多省聯(lián)考《申論》題(重慶二卷)及參考答案
- 如何自制固定翼航模
- 食堂延期合同模板(2篇)
- 2024至2030年中國小模數(shù)齒輪市場調(diào)查與行業(yè)前景預(yù)測(cè)專題研究報(bào)告
評(píng)論
0/150
提交評(píng)論