一、死鎖陷阱
關(guān)于Go的并發(fā)編程,你會(huì)遇到哪些陷阱:
- 主協(xié)程退出時(shí),所有子協(xié)程都一并退出;
- 所有子協(xié)程都已經(jīng)完成工作,但主協(xié)程和一些工作協(xié)程還存活,這是由于主協(xié)程無法獲得工作協(xié)程的完成狀態(tài);
- 多個(gè)不同的協(xié)程都鎖定了受保護(hù)的資源而且同時(shí)嘗試去獲得對(duì)方資源的時(shí)候,即死鎖。
以上陷阱的主要緣由是主協(xié)程不知道子協(xié)程的工作狀態(tài)和結(jié)果
常見解決方案:
- 簡(jiǎn)約方法:讓主協(xié)程在一個(gè)done 通道上等待,根據(jù)接收到的通道信息判斷工作是否完成。
- 等待組方法:使用sync.WaitGroup讓每個(gè)工作協(xié)程報(bào)告自己的完成狀態(tài)。
值得注意的是,無論是簡(jiǎn)約方法,還是等待組方法,都有可能產(chǎn)生死鎖:
- 等待組是當(dāng)所有工作協(xié)程都處于鎖定狀態(tài)時(shí)(等待接收通道數(shù)據(jù))調(diào)用sync.WaitGroup.Wait()會(huì)產(chǎn)生死鎖。
- 假如有若干個(gè)協(xié)程可以互相通知對(duì)方去執(zhí)行某個(gè)函數(shù)(向?qū)Ψ桨l(fā)送一個(gè)請(qǐng)求),如果這些請(qǐng)求執(zhí)行的函數(shù)中有一個(gè)函數(shù)向執(zhí)行它的調(diào)用者協(xié)程發(fā)送一些數(shù)據(jù),那就會(huì)發(fā)生死鎖。
二、善用通道
通道為并發(fā)運(yùn)行的協(xié)程之間提供了一種無鎖通信方式(無人工加解鎖),當(dāng)通道有數(shù)據(jù)傳輸時(shí),發(fā)送和接收的協(xié)程都處于同步狀態(tài)。
通道使用經(jīng)驗(yàn)1:默認(rèn)情況下,通道是雙向的,但我們通常在使用通道時(shí),通道變量是作為參數(shù)傳遞的,在通信的另一個(gè)協(xié)程里我們往往希望它單向使用該通道,要么只發(fā)送( ch<- data),要么只接收( data:= <-ch) ,所以我們往往在聲明函數(shù)的通道參數(shù)時(shí)先指定通道方向,以規(guī)范協(xié)程函數(shù)內(nèi)的通道使用,函數(shù)參數(shù)聲明單向通道會(huì)提供額外的編譯器檢查,開發(fā)時(shí)我們應(yīng)該有這種編程習(xí)慣。
通道使用經(jīng)驗(yàn)2:只有在后面要檢查通道是否關(guān)閉的時(shí)候才需要顯式的關(guān)閉通道。
例如:在一個(gè)for ... range.. 循環(huán)里,或者select通道多路復(fù)用里,或者使用<-操作符來檢查是否可以接收等情況,需要顯式關(guān)閉通道。
通道非常輕量,對(duì)系統(tǒng)資源的消耗可忽略不計(jì),簡(jiǎn)而言之,有檢查通道關(guān)閉狀態(tài)需要的情況下,才會(huì)去在發(fā)送端顯式關(guān)閉通道,以上經(jīng)驗(yàn)可應(yīng)付大部分場(chǎng)景,如有特殊需求可具體情況具體分析。
通道使用經(jīng)驗(yàn)3:應(yīng)該由發(fā)送端的協(xié)程關(guān)閉通道,而不是接收端去關(guān)閉。
三、并發(fā)安全
通常,通道傳輸值類型(如struct、數(shù)組、布爾值、整型、浮點(diǎn)型、甚至字符串)都是安全的,因?yàn)樗鼈兌际强截悅鬏?,也就是說并發(fā)訪問相同的值也沒有風(fēng)險(xiǎn)。那么你也可以想到通道傳輸指針類型和引用類型不能保證數(shù)據(jù)安全,因?yàn)槿魏蔚玫街羔橆愋突蛞妙愋偷淖兞康膮f(xié)程都可以修改數(shù)據(jù),所以當(dāng)涉及指針和引用時(shí)我們必須保證這些變量在任何時(shí)候只能被一個(gè)協(xié)程訪問得到,建議指針和引用(甚至可寫接口類型)的值訪問串行進(jìn)行。
如何安全的并發(fā)訪問指針或引用變量:
- 方法一:使用同步鎖sync.Mutex,這非常好理解,一個(gè)數(shù)據(jù)只允許一個(gè)協(xié)程獨(dú)占使用,當(dāng)其他協(xié)程也需要訪問時(shí)只能等待;
- 方法二:設(shè)定規(guī)則,編碼自律,即一旦指針或引用被發(fā)送后發(fā)送方不再訪問它,然后接收方訪問并在之后釋放指針和引用指向的值;
- 方法三:讓所有導(dǎo)出的方法不能修改該值,所有可修改值的方法都不引出,這樣外部可以引出這些方法進(jìn)行并發(fā)訪問,但內(nèi)部只允許一個(gè)協(xié)程取訪問它的非導(dǎo)出方法。