channel的作用
channel被設(shè)計(jì)用來(lái)實(shí)現(xiàn)goroutine間的通信,按照golang的設(shè)計(jì)思想:以通信的方式共享內(nèi)存。
channel的內(nèi)存布局
例如如下代碼中的make函數(shù)會(huì)在堆上分配一個(gè)runtime.hchan類型的數(shù)據(jù)結(jié)構(gòu),ch是存在于函數(shù)f棧幀上的一個(gè)指針,指向堆上的hchan數(shù)據(jù)結(jié)構(gòu)。
func f() {
ch := make(chan int)
...
}
至于為什么是堆上的一個(gè)結(jié)構(gòu)體:首先,要實(shí)現(xiàn)channel這樣的復(fù)雜功能,肯定不是幾個(gè)字節(jié)可以搞定的,所以需要一個(gè)struct來(lái)實(shí)現(xiàn);其次,這種被設(shè)計(jì)用來(lái)實(shí)現(xiàn)協(xié)程間通信的組件,其作用域和生命周期不可能僅限于某個(gè)函數(shù)內(nèi)部,所以golang直接將其分配在堆上。
channel的數(shù)據(jù)結(jié)構(gòu)
下面結(jié)合在channel中的作用,解讀一下hchan中都有哪些字段:
1)協(xié)程間通信肯定涉及到并發(fā)訪問,所以要有鎖來(lái)保護(hù)整個(gè)數(shù)據(jù)結(jié)構(gòu):
type hchan struct {
...
lock mutex
}
2)channel分為“無(wú)緩沖”和“有緩沖”兩種,對(duì)于有緩沖channel來(lái)講,需要有相應(yīng)的內(nèi)存來(lái)存儲(chǔ)數(shù)據(jù),實(shí)際上就是一個(gè)數(shù)組,需要知道數(shù)組的地址、容量、元素的大小,以及數(shù)組的長(zhǎng)度也就是已有元素個(gè)數(shù),加上這幾個(gè)字段后,上面的結(jié)構(gòu)體就變成了這樣:
type hchan struct {
qcount uint // 數(shù)組長(zhǎng)度,即已有元素個(gè)數(shù)
dataqsiz uint // 數(shù)組容量,即可容納元素個(gè)數(shù)
buf unsafe.Pointer // 數(shù)組地址
elemsize uint16 // 元素大小
...
}
3)因?yàn)間olang運(yùn)行時(shí)中內(nèi)存復(fù)制、垃圾回收等機(jī)制依賴數(shù)據(jù)的類型信息,所以hchan中還要有一個(gè)指針,指向元素類型的類型元數(shù)據(jù):
type hchan struct {
...
elemtype *_type // 元素類型
...
}
4)channel支持交替的讀寫(稱send為寫,recv為讀,更簡(jiǎn)潔),有緩沖channel內(nèi)的緩沖數(shù)組會(huì)被作為一個(gè)“環(huán)型”來(lái)使用,當(dāng)下標(biāo)超過數(shù)組容量后會(huì)回到第一個(gè)位置,所以需要有兩個(gè)字段記錄當(dāng)前讀和寫的下標(biāo)位置:
type hchan struct {
...
sendx uint // 下一次寫下標(biāo)位置
recvx uint // 下一次讀下標(biāo)位置
...
}
5)當(dāng)讀和寫操作不能立即完成時(shí),需要能夠讓當(dāng)前協(xié)程在channel上等待,當(dāng)條件滿足時(shí),要能夠立即喚醒等待的協(xié)程,所以要有兩個(gè)等待隊(duì)列,分別針對(duì)讀和寫:
type hchan struct {
...
recvq waitq // 讀等待隊(duì)列
sendq waitq // 寫等待隊(duì)列
...
}
6)channel是能夠被close的,所以要有一個(gè)字段記錄是否已經(jīng)close掉了:
type hchan struct {
...
closed uint32
...
}
最后整合起來(lái),runtime.hchan結(jié)構(gòu)是這個(gè)樣子:
type hchan struct {
qcount uint // 數(shù)組長(zhǎng)度,即已有元素個(gè)數(shù)
dataqsiz uint // 數(shù)組容量,即可容納元素個(gè)數(shù)
buf unsafe.Pointer // 數(shù)組地址
elemsize uint16 // 元素大小
closed uint32
elemtype *_type // 元素類型
sendx uint // 下一次寫下標(biāo)位置
recvx uint // 下一次讀下標(biāo)位置
recvq waitq // 讀等待隊(duì)列
sendq waitq // 寫等待隊(duì)列
lock mutex
}
本篇先到這里,至于channel的讀寫操作和select機(jī)制,留到后面的文章中解讀。