先看下源碼,源碼位于src/runtime/chan.go中
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
}
type waitq struct {
first *sudog
last *sudog
}
type sudog struct {
// The following fields are protected by the hchan.lock of the
// channel this sudog is blocking on. shrinkstack depends on
// this for sudogs involved in channel ops.
g *g
// isSelect indicates g is participating in a select, so
// g.selectDone must be CAS'd to win the wake-up race.
isSelect bool
next *sudog
prev *sudog
elem unsafe.Pointer // data element (may point to stack)
// The following fields are never accessed concurrently.
// For channels, waitlink is only accessed by g.
// For semaphores, all fields (including the ones above)
// are only accessed when holding a semaRoot lock.
acquiretime int64
releasetime int64
ticket uint32
parent *sudog // semaRoot binary tree
waitlink *sudog // g.waiting list or semaRoot
waittail *sudog // semaRoot
c *hchan // channel
}
qcount uint // 當(dāng)前隊(duì)列中剩余元素個數(shù)
dataqsiz uint // 環(huán)形隊(duì)列長度涡驮,即緩沖區(qū)的大小评架,即make(chan T,N),N.
buf unsafe.Pointer // 環(huán)形隊(duì)列指針
elemsize uint16 // 每個元素的大小
closed uint32 // 表示當(dāng)前通道是否處于關(guān)閉狀態(tài)夭坪。創(chuàng)建通道后,該字段設(shè)置為0过椎,即通道打開; 通過調(diào)用close將其設(shè)置為1室梅,通道關(guān)閉。
elemtype *_type // 元素類型疚宇,用于數(shù)據(jù)傳遞過程中的賦值亡鼠;
sendx uint和recvx uint是環(huán)形緩沖區(qū)的狀態(tài)字段,它指示緩沖區(qū)的當(dāng)前索引 - 支持?jǐn)?shù)組敷待,它可以從中發(fā)送數(shù)據(jù)和接收數(shù)據(jù)间涵。
recvq waitq // 等待讀消息的goroutine隊(duì)列
sendq waitq // 等待寫消息的goroutine隊(duì)列
lock mutex // 互斥鎖,為每個讀寫操作鎖定通道榜揖,因?yàn)榘l(fā)送和接收必須是互斥操作勾哩。
這里sudog代表goroutine。
如創(chuàng)建帶緩沖的通道:ch := make(chan int, 3)举哟,思劳,底層的數(shù)據(jù)模型如下圖:
向channel中寫入數(shù)據(jù)
ch <- 3。底層hchan數(shù)據(jù)流程如圖
發(fā)送操作流程:
1.鎖定整個通道結(jié)構(gòu)妨猩。
2.確定寫入潜叛。如果recvq不為空,嘗試recvq從等待隊(duì)列中等待goroutine册赛,然后將元素直接寫入goroutine钠导。
3.如果recvq為空,則確定緩沖區(qū)是否可用森瘪,如果可用牡属,則從當(dāng)前goroutine復(fù)制數(shù)據(jù)到緩沖區(qū)。
4.如果緩沖區(qū)已滿扼睬,則要寫入的元素將保存在當(dāng)前正在執(zhí)行的goroutine的結(jié)構(gòu)中逮栅,并且當(dāng)前goroutine將在sendq中排隊(duì)并從運(yùn)行時掛起悴势。
5.寫入完成釋放鎖。
從channel中讀取數(shù)據(jù)
ch <- 3措伐,底層hchan數(shù)據(jù)流程圖:
讀取操作概要:
1.先獲取channel全局鎖特纤。
2.嘗試從sendq等待隊(duì)列中獲取等待的goroutine。
3.如果有等待的goroutine侥加,且沒有緩沖區(qū)捧存,取出goroutine并讀取數(shù)據(jù),然后喚醒這個goroutine担败,結(jié)束讀取釋放鎖昔穴。
4.如果有等待的goroutine,且有緩沖區(qū)(緩沖區(qū)已滿)提前,從緩沖區(qū)隊(duì)首取出數(shù)據(jù)吗货,再從sendq中取出一個goroutine,將goroutine中的數(shù)據(jù)取出存入buf隊(duì)尾狈网,結(jié)束讀取釋放鎖宙搬。
5.如果沒有等待的goroutine,且緩沖區(qū)有數(shù)據(jù)拓哺,直接讀取緩沖區(qū)數(shù)據(jù)勇垛,結(jié)束讀取釋放鎖。
6.如果沒有等待的goroutine拓售,且沒有緩沖區(qū)或緩沖區(qū)為空窥摄,則當(dāng)前goroutine加入到recvq排隊(duì),進(jìn)入睡眠础淤,等待被寫的goroutine喚醒,結(jié)束讀取釋放鎖哨苛。
流程圖: