底層數(shù)據(jù)結(jié)構(gòu)
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
}
其中主要的幾個部分:
buf是有緩沖的channel所特有的結(jié)構(gòu)糖声,用來存儲緩存數(shù)據(jù)谤祖。是個循環(huán)鏈表酬核。
sendx和recvx用于記錄buf這個循環(huán)鏈表中的發(fā)送或者接收的index庆捺。
lock是個互斥鎖榨汤。
recvq和sendq分別是接收(<-channel)或者發(fā)送(channel <- xxx)的goroutine抽象出來的結(jié)構(gòu)體(sudog)的隊列,是個雙向鏈表轮蜕。
操作實現(xiàn)
channel基本所有操作都要上鎖,這保證了線程安全
channel的發(fā)送和接收都是值拷貝昨悼,其實golang都是值拷貝
發(fā)送和接收
1.加鎖
2.從buff拷貝到goroutine或從goroutine拷貝到buff
3.解鎖
channel的buff滿了的情況
channel緩存滿了,或者沒有緩存的時候跃洛,我們繼續(xù)send(ch <- xxx)或者recv(<- ch)會阻塞當(dāng)前goroutine幔戏。
發(fā)送阻塞:
這種情況goroutine會阻塞并通知調(diào)度器并主動退出所在的M讓其他的G使用,goroutine也會被抽象成含有G指針和send元素的sudog結(jié)構(gòu)體保存到hchan的sendq中等待被喚醒税课。
當(dāng)有其他G從channel取出數(shù)據(jù)時,channel會將等待隊列中的G推出痊剖,將G當(dāng)時send的數(shù)據(jù)推到buff中韩玩,然后調(diào)用Go的scheduler,喚醒G陆馁,并把G放到可運行的Goroutine隊列中找颓。
接收阻塞:
同理將阻塞的G放入recvq中。
此時當(dāng)有另一個G1向channel發(fā)送數(shù)據(jù)后叮贩,G1并沒有鎖住channel击狮,然后將數(shù)據(jù)放到緩存中,而是直接把數(shù)據(jù)從G1直接copy到了G2的棧中益老。在喚醒過程中彪蓬,G2無需再獲得channel的鎖,并從緩存中取數(shù)據(jù)捺萌。減少了內(nèi)存的copy档冬,提高了效率。