Channel
設計原理
不要通過共享內存的方式進行通信,而是應該通過通信的方式共享內存。
在主流編程語言中,多個線程傳遞數據的方式一般都是共享內存。
Go 可以使用共享內存加互斥鎖進行通信,同時也提供了一種不同的并發模型,即通信順序進程(Communicating sequential processes,CSP)。Goroutine 和 Channel 分別對應 CSP 中的實體和傳遞信息的媒介,Goroutine 之間會通過 Channel 傳遞數據。
上圖中的兩個 Goroutine,一個會向 Channel 中發送數據,另一個會從 Channel 中接收數據,它們兩者能夠獨立運行并不存在直接關聯,但是能通過 Channel 間接完成通信。
數據結構
type hchan struct {// 循環隊列// 元素數量 qcount uint // total data in the queue// 隊列的長度dataqsiz uint // size of the circular queue// 緩沖區大小 有緩沖的 channel 才有buf unsafe.Pointer // points to an array of dataqsiz elements// 已發送和接收元素在隊列中的索引sendx uint // send indexrecvx uint // receive index// 元素類型和大小elemsize uint16elemtype *_type // element type// channel 是否已關閉closed uint32// 等待接受和發送的 goroutine 隊列recvq waitq // list of recv waiterssendq 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
}
sendq
和 recvq
存儲了當前 Channel 由于緩沖區空間不足而阻塞的 Goroutine 列表,這些等待隊列使用雙向鏈表 runtime.waitq
表示,鏈表中所有的元素都是 runtime.sudog
結構,runtime.sudog
表示一個在等待列表中的 Goroutine。
type waitq struct {first *sudoglast *sudog
}
創建 channel
通道有兩個方向,發送和接收。
一般而言,使用 make 創建一個能收能發的通道:
// 無緩沖通道
ch1 := make(chan int)
// 有緩沖通道
ch2 := make(chan int, 10)
創建 chan 的函數是 makechan:
func makechan(t *chantype, size int64) *hchan
創建的 chan 是一個指針,所以我們能在函數間直接傳遞 channel,而不用傳遞 channel 的指針。
const hchanSize = unsafe.Sizeof(hchan{}) + uintptr(-int(unsafe.Sizeof(hchan{}))&(maxAlign-1))func makechan(t *chantype, size int64) *hchan {elem := t.elem// 省略了檢查 channel size,align 的代碼// ……var c *hchan// 如果元素類型不含指針 或者 size 大小為 0(無緩沖類型)// 只進行一次內存分配if elem.kind&kindNoPointers != 0 || size == 0 {// 如果 hchan 結構體中不含指針,GC 就不會掃描 chan 中的元素// 只分配 "hchan 結構體大小 + 元素大小*個數" 的內存c = (*hchan)(mallocgc(hchanSize+uintptr(size)*elem.size, nil, true))// 如果是緩沖型 channel 且元素大小不等于 0(大小等于 0的元素類型:struct{})if size > 0 && elem.size != 0 {c.buf = add(unsafe.Pointer(c), hchanSize)} else {// race detector uses this location for synchronization// Also prevents us from pointing beyond the allocation (see issue 9401).// 1. 非緩沖型的,buf 沒用,直接指向 chan 起始地址處// 2. 緩沖型的,能進入到這里,說明元素無指針且元素類型為 struct{},也無影響// 因為只會用到接收和發送游標,不會真正拷貝東西到 c.buf 處(這會覆蓋 chan的內容)c.buf = unsafe.Pointer(c)}} else {// 進行兩次內存分配操作c = new(hchan)c.buf = newarray(elem, int(size))}// 更新字段c.elemsize = uint16(elem.size)c.elemtype = elemc.dataqsiz = uint(size)return c
}