一、死鎖陷阱
關(guān)于Go的并發(fā)編程,你會遇到哪些陷阱:
- 主協(xié)程退出時洁仗,所有子協(xié)程都一并退出;
- 所有子協(xié)程都已經(jīng)完成工作性锭,但主協(xié)程和一些工作協(xié)程還存活赠潦,這是由于主協(xié)程無法獲得工作協(xié)程的完成狀態(tài);
- 多個不同的協(xié)程都鎖定了受保護(hù)的資源而且同時嘗試去獲得對方資源的時候草冈,即死鎖她奥。
以上陷阱的主要緣由是主協(xié)程不知道子協(xié)程的工作狀態(tài)和結(jié)果
常見解決方案:
- 簡約方法:讓主協(xié)程在一個done 通道上等待,根據(jù)接收到的通道信息判斷工作是否完成怎棱。
- 等待組方法:使用sync.WaitGroup讓每個工作協(xié)程報告自己的完成狀態(tài)哩俭。
值得注意的是,無論是簡約方法拳恋,還是等待組方法凡资,都有可能產(chǎn)生死鎖:
- 等待組是當(dāng)所有工作協(xié)程都處于鎖定狀態(tài)時(等待接收通道數(shù)據(jù))調(diào)用sync.WaitGroup.Wait()會產(chǎn)生死鎖。
- 假如有若干個協(xié)程可以互相通知對方去執(zhí)行某個函數(shù)(向?qū)Ψ桨l(fā)送一個請求)谬运,如果這些請求執(zhí)行的函數(shù)中有一個函數(shù)向執(zhí)行它的調(diào)用者協(xié)程發(fā)送一些數(shù)據(jù)隙赁,那就會發(fā)生死鎖。
二梆暖、善用通道
通道為并發(fā)運行的協(xié)程之間提供了一種無鎖通信方式(無人工加解鎖)伞访,當(dāng)通道有數(shù)據(jù)傳輸時,發(fā)送和接收的協(xié)程都處于同步狀態(tài)轰驳。
通道使用經(jīng)驗1:默認(rèn)情況下厚掷,通道是雙向的,但我們通常在使用通道時级解,通道變量是作為參數(shù)傳遞的冒黑,在通信的另一個協(xié)程里我們往往希望它單向使用該通道,要么只發(fā)送( ch<- data)蠕趁,要么只接收( data:= <-ch) 薛闪,所以我們往往在聲明函數(shù)的通道參數(shù)時先指定通道方向辛馆,以規(guī)范協(xié)程函數(shù)內(nèi)的通道使用俺陋,函數(shù)參數(shù)聲明單向通道會提供額外的編譯器檢查,開發(fā)時我們應(yīng)該有這種編程習(xí)慣昙篙。
通道使用經(jīng)驗2:只有在后面要檢查通道是否關(guān)閉的時候才需要顯式的關(guān)閉通道腊状。
例如:在一個for ... range.. 循環(huán)里,或者select通道多路復(fù)用里苔可,或者使用<-操作符來檢查是否可以接收等情況缴挖,需要顯式關(guān)閉通道。
通道非常輕量焚辅,對系統(tǒng)資源的消耗可忽略不計映屋,簡而言之苟鸯,有檢查通道關(guān)閉狀態(tài)需要的情況下,才會去在發(fā)送端顯式關(guān)閉通道棚点,以上經(jīng)驗可應(yīng)付大部分場景早处,如有特殊需求可具體情況具體分析。
通道使用經(jīng)驗3:應(yīng)該由發(fā)送端的協(xié)程關(guān)閉通道瘫析,而不是接收端去關(guān)閉砌梆。
三、并發(fā)安全
通常贬循,通道傳輸值類型(如struct咸包、數(shù)組、布爾值杖虾、整型烂瘫、浮點型、甚至字符串)都是安全的亏掀,因為它們都是拷貝傳輸忱反,也就是說并發(fā)訪問相同的值也沒有風(fēng)險。那么你也可以想到通道傳輸指針類型和引用類型不能保證數(shù)據(jù)安全滤愕,因為任何得到指針類型或引用類型的變量的協(xié)程都可以修改數(shù)據(jù)温算,所以當(dāng)涉及指針和引用時我們必須保證這些變量在任何時候只能被一個協(xié)程訪問得到,建議指針和引用(甚至可寫接口類型)的值訪問串行進(jìn)行间影。
如何安全的并發(fā)訪問指針或引用變量:
- 方法一:使用同步鎖sync.Mutex注竿,這非常好理解,一個數(shù)據(jù)只允許一個協(xié)程獨占使用魂贬,當(dāng)其他協(xié)程也需要訪問時只能等待巩割;
- 方法二:設(shè)定規(guī)則,編碼自律付燥,即一旦指針或引用被發(fā)送后發(fā)送方不再訪問它宣谈,然后接收方訪問并在之后釋放指針和引用指向的值;
- 方法三:讓所有導(dǎo)出的方法不能修改該值键科,所有可修改值的方法都不引出闻丑,這樣外部可以引出這些方法進(jìn)行并發(fā)訪問,但內(nèi)部只允許一個協(xié)程取訪問它的非導(dǎo)出方法勋颖。