- 并發(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í),有以下方法可以解決:- 我們必須保證這些值在任何時(shí)候只能被一個(gè)
goroutine訪問得到,也就是說,對(duì)這些值的訪問必須是串行進(jìn)行的 - 設(shè)定一個(gè)規(guī)則,一旦
指針或者引用發(fā)送之后發(fā)送方就不會(huì)再訪問它,然后讓接收者來(lái)訪問和釋放指針或者引用指向的值 - 讓所有導(dǎo)出的方法不能修改其值,所有可修改值的方法都不引出。這樣外部可以通過引出的這些方法進(jìn)行并發(fā)訪問,但是內(nèi)部實(shí)現(xiàn)只允許一個(gè)
goroutine去訪問它的非導(dǎo)出方法
- 我們必須保證這些值在任何時(shí)候只能被一個(gè)
- Go語(yǔ)言里還可以傳送
接口類型的值,也就是說,只要這個(gè)值實(shí)現(xiàn)了接口定義的所有方法,就可以以這個(gè)接口的方式在通道里傳輸 - 只讀型接口的值可以在任意多個(gè)
goroutine里使用(除非文檔特別聲明),但是對(duì)于某些值,它雖然實(shí)現(xiàn)了這個(gè)接口的方法,但是某些方法也修改了這個(gè)值本身的狀態(tài),就必須和指針一樣處理,讓它的訪問串行化

