channel通道
golang的并發(fā)模型是序列通信處理CSP(communicating sequential process)——使用通信來共享內(nèi)存 鸠真,避免goroutine因競爭共享內(nèi)存頻繁加鎖產(chǎn)生的性能問題屑咳。
基本數(shù)據(jù)結(jié)構(gòu)
golang中提供了一個特殊的類型channel實現(xiàn)goroutine之間的通信耗跛。channel類似于隊列杖刷,先進先出竹勉。channel數(shù)據(jù)結(jié)構(gòu)源碼在src/runtime/chan.go下。
chan 使用 hchan 表示肿嘲,它的傳參與賦值始終都是指針形式融击,每個 hchan 對象代表著一個 chan。
- hchan 中包含一個緩沖區(qū) buf睦刃,它表示已經(jīng)發(fā)送但是還未被接收的數(shù)據(jù)緩存砚嘴。buf 的大小由創(chuàng)建 chan 時的參數(shù)來決定。qcount 表示當(dāng)前緩沖區(qū)中有效數(shù)據(jù)的總量涩拙,dataqsiz 表示緩沖區(qū)的大小际长,對于無緩沖區(qū)通道而言 dataqsiz 的值為 0。如果 qcount 和 dataqsiz 的值相同兴泥,則表示緩沖區(qū)用完了工育。
- buf ** 緩沖區(qū)表示的是一個環(huán)形隊列** 。其中 **sendx ** 表示下一個發(fā)送的地址搓彻, **recvx ** 表示下一個接收的地址如绸。
- elemtype 是channel的中存放的具體類型嘱朽,每一個通道都是一個具體類型的導(dǎo)管,也就是聲明channel的時候需要為其指定元素類型怔接。
- recvq 表示等待接收的 sudog (goroutine)列表搪泳,一個接收語句執(zhí)行時,如果緩沖區(qū)沒有數(shù)據(jù)而且當(dāng)前沒有別的發(fā)送者在等待扼脐,那么執(zhí)行者 goroutine 會被掛起岸军,并且將對應(yīng)的 sudog 對象放到 recvq 中。
- sendq 類似于 recvq瓦侮,一個發(fā)送語句執(zhí)行時艰赞,如果緩沖區(qū)已經(jīng)滿了,而且沒有接收者在等待肚吏,那么執(zhí)行者 goroutine 會被掛起方妖,并且將對應(yīng)的 sudog 放到 sendq 中。
- closed 表示通道是否已經(jīng)被關(guān)閉罚攀,0 代表沒有被關(guān)閉党觅,非 0 值代表已經(jīng)被關(guān)閉。
- lock 用于對 hchan 加鎖
type hchan struct {
qcount uint // total data in the queue
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // points to an array of dataqsiz elements
elemsize uint16
closed uint32
elemtype *_type // element type
sendx uint // send index
recvx uint // receive index
recvq waitq // list of recv waiters
sendq waitq // list of send waiters
// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex
}
創(chuàng)建一個channel
var name chan elemType //聲明中需要指定channel的具體類型 eg. var c chan int 聲明了一個int的channel
我們使用make()
方法創(chuàng)建斋泄,可以指定chan的緩存區(qū)大小仔役。
c1 := make(chan int, 10) //make創(chuàng)建一個int類型的channel,容量大小為10
c2 := make(chan int) // 無緩存的channel
make()函數(shù)最后會調(diào)用底層的makechan()函數(shù)是己,返回一個通道的指針。當(dāng)我們復(fù)制一個channel或用于函數(shù)參數(shù)傳遞時任柜,我們只是拷貝了一個channel引用卒废,因此調(diào)用者和被調(diào)用者將引用同一個channel對象。和其它的引用類型一樣宙地,channel的零值也是nil摔认。
兩個相同類型的channel可以使用==運算符比較。如果兩個channel引用的是相同的對象宅粥,那么比較的結(jié)果為真参袱。一個channel也可以和nil進行比較。
通道的操作
發(fā)送(直譯秽梅,其實感覺翻譯成存入更好)
ch := make(chan int,0) // 創(chuàng)建一個無緩存區(qū)的int channel
ch <- 998 // 向通道發(fā)送一個int類型的值10
接收(取出)
receiver := <-ch //將通道ch的值取出抹蚀,賦值給變量receiver
<- ch //將通道ch的值取出,忽略結(jié)果
關(guān)閉
close(ch) // 關(guān)閉通道
關(guān)于關(guān)閉通道需要注意的事情是企垦,只有在通知接收方goroutine所有的數(shù)據(jù)都發(fā)送完畢的時候才需要關(guān)閉通道环壤。通道是可以被垃圾回收機制回收的,它和關(guān)閉文件是不一樣的钞诡,在結(jié)束操作之后關(guān)閉文件是必須要做的郑现,但關(guān)閉通道不是必須的湃崩。
關(guān)閉后的通道有以下特點:
- 對一個關(guān)閉的通道再發(fā)送值就會導(dǎo)致panic。
- 對一個關(guān)閉的通道進行接收會一直獲取值直到通道為空接箫。
- 對一個關(guān)閉的并且沒有值的通道執(zhí)行接收操作會得到對應(yīng)類型的零值攒读。
- 關(guān)閉一個已經(jīng)關(guān)閉的通道會導(dǎo)致panic。
channel的種類
不帶緩存的channel
一個基于無緩存Channels的發(fā)送操作將導(dǎo)致發(fā)送者goroutine阻塞辛友,直到另一個goroutine在相同的Channels上執(zhí)行接收操作薄扁,當(dāng)發(fā)送的值通過Channels成功傳輸之后,兩個goroutine可以繼續(xù)執(zhí)行后面的語句瞎领。反之泌辫,如果接收操作先發(fā)生,那么接收者goroutine也將阻塞九默,直到有另一個goroutine在相同的Channels上執(zhí)行發(fā)送操作震放。
基于無緩存Channels的發(fā)送和接收操作將導(dǎo)致兩個goroutine做一次同步操作。 因為這個原因驼修,無緩存Channels有時候也被稱為同步Channels 殿遂。當(dāng)通過一個無緩存Channels發(fā)送數(shù)據(jù)時,接收者收到數(shù)據(jù)發(fā)生在再次喚醒發(fā)送者goroutine之前乙各。
單向channel
當(dāng)一個channel作為一個函數(shù)參數(shù)時墨礁,它一般總是被專門用于只發(fā)送或者只接收。為了表明這種意圖并防止被濫用耳峦,Go語言的類型系統(tǒng)提供了單方向的channel類型恩静,分別用于只發(fā)送或只接收的channel。類型 chan<- int
表示一個只發(fā)送int的channel蹲坷,只能發(fā)送不能接收驶乾。相反运提,類型<-chan int
表示一個只接收int的channel彤恶,只能接收不能發(fā)送护昧。(箭頭<-
和關(guān)鍵字chan的相對位置表明了channel的方向 侮腹。)這種限制將在編譯期檢測嬉橙。
func f1(out chan<- int) {}
func f2(out chan<- int, in <-chan int){}
在調(diào)用f1或f2的時候挪圾,傳入的chan類型參數(shù)會自動隱式轉(zhuǎn)換為chan<- int
和<-chan int
類型丛版,這種轉(zhuǎn)換只是單向的产捞,沒有單向channel類型轉(zhuǎn)換為chan類型乞旦。
因為關(guān)閉操作只用于斷言不再向channel發(fā)送新的數(shù)據(jù)贼穆,所以只有在發(fā)送者所在的goroutine才會調(diào)用close函數(shù),因此對一個只接收的channel調(diào)用close將是一個編譯錯誤杆查。
帶緩存的channel
帶緩存的Channel內(nèi)部持有一個元素隊列扮惦。隊列的最大容量是在調(diào)用make函數(shù)創(chuàng)建channel時通過第二個參數(shù)指定的。通過內(nèi)置函數(shù)cap()
可以獲得channel的容量亲桦。對于內(nèi)置的len函數(shù)崖蜜,如果傳入的是channel浊仆,那么將返回channel內(nèi)部緩存隊列中有效元素的個數(shù)。因為在并發(fā)程序中該信息會隨著接收操作而失效豫领,但是它對某些故障診斷和性能優(yōu)化會有幫助抡柿。
package main
import "fmt"
func main() {
ch := make(chan int, 10) // 創(chuàng)建一個最大容量為10的channel
ch <- 233
ch <- 2
ch <- 3
fmt.Println(cap(ch)) // 10
fmt.Println(len(ch)) // 3
fmt.Println(<-ch) // 233
fmt.Println(len(ch)) // 2
}
向緩存Channel的發(fā)送操作就是向內(nèi)部緩存隊列的尾部插入元素,接收操作則是從隊列的頭部刪除元素等恐。如果內(nèi)部緩存隊列是滿的洲劣,那么發(fā)送操作將阻塞直到因另一個goroutine執(zhí)行接收操作而釋放了新的隊列空間。相反课蔬,如果channel是空的囱稽,接收操作將阻塞直到有另一個goroutine執(zhí)行發(fā)送操作而向隊列插入元素。