Go 語(yǔ)言程序設(shè)計(jì)——并發(fā)編程(1)

  • 并發(fā)編程可以讓開發(fā)者實(shí)現(xiàn)并行的算法以及編寫充分利用多處理器和多核性能的程序
  • 編寫、維護(hù)和調(diào)試并發(fā)程序相比單線程程序而言要困難很多
  • Go語(yǔ)言的并發(fā)解決方案有3個(gè)優(yōu)點(diǎn):
    • Go語(yǔ)言對(duì)并發(fā)編程提供了上層支持,因此正確處理并發(fā)是很容易做到的
    • 用來(lái)處理并發(fā)的 goroutine 比線程更加輕量
    • 并發(fā)程序的內(nèi)存管理有時(shí)候是非常復(fù)雜的,而Go語(yǔ)言提供了自動(dòng)垃圾回收機(jī)制
  • Go語(yǔ)言為并發(fā)編程而內(nèi)置的上層API基于CSP模型(Communicating Sequential Processes)
  • Go語(yǔ)言通過線程安全的通道發(fā)送和接受數(shù)據(jù)以實(shí)現(xiàn)同步
  • Go和其他語(yǔ)言一樣也提供了對(duì)底層功能的支持。在標(biāo)準(zhǔn)庫(kù)的 sync/atomic包 里提供了最底層的原子操作功能,包括相加、比較交換操作
  • Go語(yǔ)言的sync包還提供了非常方便的底層并發(fā)原語(yǔ):條件等待互斥量
  • Go語(yǔ)言推薦程序員在并發(fā)編程時(shí)使用語(yǔ)言的上層功能,例如 通道goroutine

關(guān)鍵概念

  • 在并發(fā)編程里,我們通常想將一個(gè)過程切分成幾塊,然后讓每個(gè) goroutine 各自負(fù)責(zé)一塊工作
  • 我們將 main() 函數(shù)所在的 goroutine 稱為 主goroutine,其他附加創(chuàng)建用來(lái)負(fù)責(zé)處理相應(yīng)工作的 goroutine 簡(jiǎn)稱為 工作goroutine
  • Go語(yǔ)言雖然通過上層的API來(lái)處理并發(fā),但仍有必要去避免一些陷阱:
    • 主goroutine 退出后,其他的 工作 goroutine 也會(huì)自動(dòng)退出,所以我們必須非常小心地保證所有 工作 goroutine 都完成后才讓 主goroutine 退出
    • 容易發(fā)生 死鎖,這個(gè)問題有一點(diǎn)和第一個(gè)陷阱是剛好相反的: 所有的工作已經(jīng)完成了,但是 主 goroutine工作 goroutine 還存活,這種情況通常是由于工作完成了但是 主 goroutine 無(wú)法獲得 工作 goroutine 的完成狀態(tài)
    • 死鎖的另一種情況就是,當(dāng)兩個(gè)不同的 goroutine(或者 線程)都鎖定了受保護(hù)的資源而且同時(shí)嘗試去獲得對(duì)方資源的時(shí)候(Go語(yǔ)言里并不多,因?yàn)镚o程序可以使用 通道 來(lái)避免使用鎖)
  • 為了避免程序提前退出或不能正常退出,常見的做法是讓 主goroutine 在一個(gè) done通道 上等待,根據(jù)接收到的消息來(lái)判斷工作是否完成了
  • 另外一種避免這些陷阱的辦法就是使用 sync.WaitGroup 來(lái)讓每個(gè) 工作goroutine 報(bào)告自己的完成狀態(tài)(使用 sync.WaitGroup 本身也會(huì)產(chǎn)生死鎖,特別是當(dāng)所有 工作goroutine 都處于鎖定狀態(tài)的時(shí)候(等待接受通道的數(shù)據(jù))調(diào)用 sync.WaitGroup.Wait()
  • 就算只使用 通道,在Go語(yǔ)言里仍然可能發(fā)生死鎖:
    *假如我們有若干個(gè) goroutine 可以相互通知對(duì)方去執(zhí)行某個(gè)函數(shù)(向?qū)Ψ桨l(fā)一個(gè)請(qǐng)求),現(xiàn)在,如果這些被請(qǐng)求執(zhí)行的函數(shù)中有一個(gè)函數(shù)向執(zhí)行它的 goroutine 發(fā)送了一些東西,例如 數(shù)據(jù),死鎖就發(fā)生了
    兩個(gè)或多個(gè)阻塞線程試圖取得對(duì)方的鎖

    一個(gè)試圖用對(duì)自身的請(qǐng)求來(lái)服務(wù)于請(qǐng)求的goroutine
  • 通道為并發(fā)運(yùn)行的 goroutine 之間提供了一種無(wú)鎖通信方式(盡管實(shí)現(xiàn)內(nèi)部可能使用了鎖,但無(wú)需我們關(guān)心)
  • 當(dāng)一個(gè)通道發(fā)生通信時(shí),發(fā)送通道接受通道(包括它們對(duì)應(yīng)的 goroutine)都處于同步狀態(tài)
  • 默認(rèn)情況下,通道是雙向的,既可以往里面發(fā)送數(shù)據(jù)也可以從里面接收數(shù)據(jù)
  • 我們經(jīng)常將一個(gè)通道作為參數(shù)進(jìn)行傳遞而只希望對(duì)方是單向使用的,這個(gè)時(shí)候我們可以指定通道的方向
  • 在通道里傳輸布爾類型整型或者 float64 類型的值都是安全的,因?yàn)樗鼈兌际峭ㄟ^復(fù)制的方式來(lái)傳送的,所以在并發(fā)時(shí)如果不小心大家都訪問了一個(gè)相同的值,這也沒有什么風(fēng)險(xiǎn)(發(fā)送字符串也是安全的,因?yàn)镚o語(yǔ)言里不允許修改字符串
  • Go語(yǔ)言并不保證在通道里發(fā)送指針或者引用類型(如切片映射)的安全性,因?yàn)橹羔樦赶虻膬?nèi)容或者所引用的值可能在對(duì)方接收到時(shí)已被發(fā)送方修改
  • 當(dāng)涉及指針引用時(shí),有以下方法可以解決:
    1. 我們必須保證這些值在任何時(shí)候只能被一個(gè)goroutine訪問得到,也就是說,對(duì)這些值的訪問必須是串行進(jìn)行的
    2. 設(shè)定一個(gè)規(guī)則,一旦指針或者引用發(fā)送之后發(fā)送方就不會(huì)再訪問它,然后讓接收者來(lái)訪問和釋放指針或者引用指向的值
    3. 讓所有導(dǎo)出的方法不能修改其值,所有可修改值的方法都不引出。這樣外部可以通過引出的這些方法進(jìn)行并發(fā)訪問,但是內(nèi)部實(shí)現(xiàn)只允許一個(gè)goroutine去訪問它的非導(dǎo)出方法
  • Go語(yǔ)言里還可以傳送接口類型的值,也就是說,只要這個(gè)值實(shí)現(xiàn)了接口定義的所有方法,就可以以這個(gè)接口的方式在通道里傳輸
  • 只讀型接口的值可以在任意多個(gè)goroutine里使用(除非文檔特別聲明),但是對(duì)于某些值,它雖然實(shí)現(xiàn)了這個(gè)接口的方法,但是某些方法也修改了這個(gè)值本身的狀態(tài),就必須和指針一樣處理,讓它的訪問串行化
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容