【博文目錄>>>】 【項目地址>>>】
chan.go是go語言通道實現,通道結構的定義,接收和發送的操作都此文件中實現。
通道的結構
hchan是通道表示的基本結構,其內容表示如下:
一些特殊情況
- 當dataqsiz=0時:說明這是一個無緩衝對列
- 當dataqsiz>0時,說明是一個緩衝對列
type hchan struct {
qcount uint // 隊列中的數據總數
dataqsiz uint // 循環隊列的大小
buf unsafe.Pointer // 指向dataqsiz數組中的一個元素
elemsize uint16
closed uint32 // 非0表示通道已經關閉
elemtype *_type // 元素類型
sendx uint // 發送索引
recvx uint // 接收索引
recvq waitq // 所有等待的接收者
sendq waitq // 所有等待的發送者
// 鎖保護hchan中的所有字段,以及此通道上阻止的sudogs中的多個字段。
// 保持該鎖狀態時,請勿更改另一個G的狀態(特別是不要準備好G),因爲這會因堆棧收縮而死鎖。
// Question: sudogs?參見runtime/runtime2.go中的sudog結構
lock mutex
}
通道的發送和接收
通道的發送和接收都是使用了一個名爲waitq的等待對例,每個接收者和發送者都是一個sudog結構,此構在runtiem/runtime2.go文件中定義,在之後的源碼分析中會仔細說明。
type waitq struct {
first *sudog // 隊列頭
last *sudog // 隊列尾
}
通道的對齊方法式
從文件的源碼中可以知道,在內存中通道是以8字節的方式進行對齊的,內存分配不是8個字節會自動對對齊
const (
maxAlign = 8 // 8字節對齊
hchanSize = unsafe.Sizeof(hchan{}) + uintptr(-int(unsafe.Sizeof(hchan{}))&(maxAlign-1))
debugChan = false
)
通道的使用
通道的創建的方法
在源碼中通道的創建有三個實現方法
- func reflect_makechan(t *chantype, size int) *hchan {…}
- func makechan64(t *chantype, size int64) *hchan {…}
- func makechan(t *chantype, size int) *hchan {…}
其本質是調用最後一個通道的創建方法。
通道的創建過程
方法func makechan(t *chantype, size int) *hchan {...}
說明了通道的整個創建過程。
/**
* 創建通道
* @param t 通道類型指針
* @param size 通道大小,0表示無緩衝通道
* @return
**/
func makechan(t *chantype, size int) *hchan {...}
通道創建要通過以過幾個步驟。
- 1、檢查通道元素類型的size,如果
elem.size >= 1<<16
,則創建失敗。 - 2、檢查通道和元素的對齊,如果
hchanSize%maxAlign != 0 || elem.align > maxAlign
,則通道創建失敗 - 3、計算通道需要分配的內存,如果
overflow || mem > maxAlloc-hchanSize || size < 0
,則通道創建失敗- 3.1、overflow:表示內存內存計算有溢出
- 3.2、mem > maxAlloc-hchanSize:表示內存分配置超出了限制
- 3.3、size < 0:表示通道緩衝爲負數
- 4、根據不同條件進行內存分配
- 4.1、mem == 0:隊列或者元素大小爲0,這是表示元素或者隊列大小爲0,直接分配hchanSize大小的內存,緩衝地址向自身
- 4.2、elem.ptrdata == 0:元素不包含指針,直接分配hchanSize+mem大小的內存,並且緩衝地址向自身+hchanSize
- 4.3、其他情況(元素包含指針):使用new的方法創建一個hchan,分配mem大小的內存,並且緩衝地址向內存分配的地址
- 5、最後設置通道結構的其他值
向通道中發送數據
向通道中發送數據的方法一共有四個,如下所示
- func chansend1(c *hchan, elem unsafe.Pointer) {…}
- func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {…}
- func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {…}
- func sendDirect(t *_type, sg *sudog, src unsafe.Pointer) {…}
chansend1方法是go編譯代碼中c <- x的入口點,即當我們編寫代碼 c <- x時,就是調用此方法。chansend1方法本質還是調用chansend方法進行處理
這四個方法的調用關係:chansend1 -> chansend -> send -> sendDirect
chansend方法
chansend方法的執行代表了事個數據的發送過程,方法簽名如下:
/**
* 通用單通道發送/接收
* 如果block不爲nil,則協議將不會休眠,但如果無法完成則返回。
*
* 當涉及休眠的通道已關閉時,可以使用g.param == nil喚醒休眠。
* 最容易循環並重新運行該操作; 我們將看到它現已關閉。
* @param c 通道對象
* @param ep 元素指針
* @param block 是否阻塞
* @param callerpc 調用者指針
* @return bool true:表示發送成功
**/
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {...}
通道數據發送的處理過程:
- 1、如果通道爲空
- 1.1、通道是非阻塞的,則返回false,表示發送失敗,方法結束
- 1.2、通道是阻塞的,則會阻塞當前goroutine,同時會執行一個throw方法,方法結束
- 2、檢查通道和數據狀態,2.1,2.2,2.3同時滿足時發關失敗,方法結束
- 2.1、!block:通道非阻塞
- 2.2、c.closed == 0:通道未關閉
- 2.3、((c.dataqsiz == 0 && c.recvq.first == nil) || (c.dataqsiz > 0 && c.qcount == c.dataqsiz))
- 2.3.1、(c.dataqsiz == 0 && c.recvq.first == nil):通道中沒有數據,並且沒有接收者
- 2.3.2、(c.dataqsiz > 0 && c.qcount == c.dataqsiz):通道中有數據,並且已經滿了
- 3、對通道進行鎖定
- 4、判斷通道是否已經關閉,如果已經關閉,就解鎖通道,並且拋出panic。方法結束
- 5、在接收者隊列中找出一個最先入隊的接收者,如果有,就調用send方法進行發送,返回true,方法結束
- 6、如果沒有找到接收者,並且c.qcount < c.dataqsiz,即通道的發送緩衝區未滿,將要發送的數據拷貝到通道緩衝區,更新相關的計數據信息,並釋放鎖,返回true,方法結束
- 7、沒有找到接收都,並且沒有緩衝可用,非阻塞方式,則解鎖通道,返回false,發送失敗
- 8、沒有找到接收者,並且沒有緩衝可用,阻塞方式。獲取gp(g)和mysg(sudog),並且將發送數據掛到mysg上,將mysg加到發送隊列。調用gopark訪求將當前goroutine阻塞,直到被恢復。
- 9、在恢復後我們還要將發送數據保活,以確保數據正確被接收者複製出去了。
- 10、檢查goroutine狀態,如果mysg != gp.waiting說明被破壞了,執行throw,方法結束
- 11、如果gp.param == nil,說明喚醒有問題,
- 11.1、如果通道未關閉,則說明是僞喚醒,執行throw方法結束
- 11.2、如果通道關閉,則panic,在關閉的通道中進行了發送消息。
- 12、最後是清理數據,並且釋放mysg
其他相關方法
/**
* 編譯器實現,將goroutine的select receive(v = <-c)語句轉成對應的方法執行
* select {
* case v = <-c:
* ... foo
* default:
* ... bar
* }
*
* => (實際對應)
*
* if selectnbrecv(&v, c) {
* ... foo
* } else {
* ... bar
* }
*/
func selectnbsend(c *hchan, elem unsafe.Pointer) (selected bool) {...}
從通道中接收數據
chan.go文件中一共有五個方法實現用於接收通道內容,他們分別是:
- func chanrecv1(c *hchan, elem unsafe.Pointer) {…},
- func chanrecv2(c *hchan, elem unsafe.Pointer) (received bool) {…}
- func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {…}
- func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {…}
- func recvDirect(t *_type, sg *sudog, dst unsafe.Pointer) {…}
其調用鏈 (chanrecv1|chanrecv2) -> chanrecv -> recv -> recvDirect
其中chanrecv1方法是go代碼<-c
的入口點
chanrecv方法
chanrecv方法實現的通道接收的主要功能,其方法簽名:
/**
* 通用單通道發送/接收
* 如果block不爲nil,則協議將不會休眠,但如果無法完成則返回。
*
* 當涉及休眠的通道已關閉時,可以使用g.param == nil喚醒休眠。
* 最容易循環並重新運行該操作; 我們將看到它現已關閉。
* @param c 通道對象
* @param ep 元素指針
* @param block 是否阻塞
* @param callerpc 調用者指針
* @return bool true:表示發送成功
**/
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {...}
通道數據接收的處理過程如下:
- 1、如通道接收通道爲nil
- 1.1、如果接收是非阻塞的,則返回false,方法結束
- 1.2、如果接收是阻塞的,則阻塞goroutine,即此goroutine會永久阻塞
- 2、當2.1,2.2,2.3同時滿足時,則返回false,方法結束
- 2.1、!block == true,即非阻塞
- 2.2、c.closed == 0,通道未關閉
- 2.3、2.3.1,2.3.2兩個滿足其中一個
- 2.3.1、c.dataqsiz == 0 && c.sendq.first == nil,通道中沒有數據,並且沒有發送者
- 2.3.2、c.dataqsiz > 0 && atomic.Loaduint(&c.qcount) == 0,通道中有空間,但沒有數據
- 3、加鎖
- 4、如果通道已經關閉,並且沒有數據,則解鎖,進行內存清理,返回(true, false),方法結束
- 5、從發送隊列中取隊頭髮送者,如果不爲空,就用發送者發送的數據,返回(true, true),方法結束
- 6、沒有發送者,數據隊列不爲空(c.qcount > 0),直接從數據隊列中接收數據,並且更新隊列計數和接收索引指針,然後複製數據,清理內存,釋放鎖,最後返回(true, true),方法結束
- 7、沒有發送者,並且隊列爲空,並且是非阻塞狀態,則釋放鎖,返回(false, false),方法結束
- 8、沒有找到發送都,並且沒有緩衝可用,阻塞方式。獲取gp(g)和mysg(sudog),並且將接收數據指針掛到mysg上,將mysg加到接收隊列。調用gopark訪求將當前goroutine阻塞,直到被恢復。
- 9、在恢復後我們還是判斷是否是意外喚醒,如果是,就panic,方法結束
- 10、進行清理工作,釋放sudog,返回(true, !closed)
recv方法
通道另一個核心方法就是recv,其方法簽名如下:
/**
* recv在完整通道c上處理接收操作。
* 有2個部分:
* 1)將發送方sg發送的值放入通道中,並喚醒發送方以繼續進行。
* 2)接收方接收到的值(當前G)被寫入ep。
* 對於同步通道,兩個值相同。
* 對於異步通道,接收者從通道緩衝區獲取數據,而發送者的數據放入通道緩衝區。
* 頻道c必須已滿且已鎖定。 recv用unlockf解鎖c。
* sg必須已經從c中出隊。
* 非nil必須指向堆或調用者的堆棧。
* @param c 通道對象針對
* @param sg sudog指針
* @param ep 用戶接收元素的指針
* @param unlockf 解鎖函數
* @param skip
* @return
**/
func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {...}
recv接收數據的過程
- 1、對於無緩衝通道(c.dataqsiz == 0),當數據指針不空的時候,直接拷貝發送指針所指的數據
- 2、對於緩衝通道,將數據寫入到緩衝中,並且更新緩衝計數,接收索引,發送索引
- 3、進行goroutine相關處理,釋放鎖,並且將當前goroutine置於等待
其他相關方法
/**
* 編譯器實現,將goroutine的select receive(v = <-c)語句轉成對應的方法執行
* select {
* case v = <-c:
* ... foo
* default:
* ... bar
* }
*
* => (實際對應)
*
* if selectnbrecv(&v, c) {
* ... foo
* } else {
* ... bar
* }
**/
func selectnbrecv(elem unsafe.Pointer, c *hchan) (selected bool) {...}
/**
* 編譯器實現,將goroutine的select receive(case v, ok = <-c:)語句轉成對應的方法執行
* select {
* case v, ok = <-c:
* ... foo
* default:
* ... bar
* }
*
* => (實際對應)
*
* if c != nil && selectnbrecv2(&v, &ok, c) {
* ... foo
* } else {
* ... bar
* }
**/
func selectnbrecv2(elem unsafe.Pointer, received *bool, c *hchan) (selected bool) {...}
通道關閉
chan.go中只有一個關閉通道的方法
- func closechan(c *hchan) {…}
通過的關閉過程 - 1、如果通道爲nil,則painc,方法結束
- 2、鎖定通道
- 3、如果通道已經關閉了,解鎖通道,並且panic,方法結束,即通道只能關閉一次
- 4、標記通道已經關閉
- 5、釋放所有的接收者,將接收者入gList隊列
- 6、釋放所有的發送者,將發送者入gList隊列
- 7、釋放鎖
- 8、將gLsit中的元素都標記成進入goready狀態
源碼
package runtime
// This file contains the implementation of Go channels.
// Invariants:
// At least one of c.sendq and c.recvq is empty,
// except for the case of an unbuffered channel with a single goroutine
// blocked on it for both sending and receiving using a select statement,
// in which case the length of c.sendq and c.recvq is limited only by the
// size of the select statement.
//
// For buffered channels, also:
// c.qcount > 0 implies that c.recvq is empty.
// c.qcount < c.dataqsiz implies that c.sendq is empty.
/**
* 此文件包含Go渠道的實現。此包中所使用的類型大部分定義在:runtime/type.go文件中
*
* 不變量:
* c.sendq和c.recvq中的至少一個爲空,但在無緩衝通道上阻塞了單個goroutine以便使用select語句發送和接收的情況除外,在這種情況下,
* c.sendq的長度 而c.recvq僅受select語句的大小限制。
*
* 對於緩衝通道,還:
* c.qcount> 0表示c.recvq爲空。
* c.qcount <c.dataqsiz表示c.sendq爲空。
*/
import (
"runtime/internal/atomic"
"runtime/internal/math"
"unsafe"
)
const (
maxAlign = 8 // 8字節對齊
hchanSize = unsafe.Sizeof(hchan{}) + uintptr(-int(unsafe.Sizeof(hchan{}))&(maxAlign-1))
debugChan = false
)
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 // 指向dataqsiz數組中的一個元素
elemsize uint16
closed uint32 // 非0表示通道已經關閉
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.
// 鎖保護hchan中的所有字段,以及此通道上阻止的sudogs中的多個字段。
// 保持該鎖狀態時,請勿更改另一個G的狀態(特別是不要準備好G),因爲這會因堆棧收縮而死鎖。
// Question: sudogs?參見runtime/runtime2.go中的sudog結構
lock mutex
}
/**
* 等待隊列數據結棍
*/
type waitq struct {
first *sudog // 隊列頭
last *sudog // 隊列尾
}
//go:linkname reflect_makechan reflect.makechan
/**
* 通過反射創建通道,
* @param
* @return
**/
func reflect_makechan(t *chantype, size int) *hchan {
return makechan(t, size) // 在reflect/value.go/makechan方法中實現
}
/**
* 創建通道
* @param t 通道類型
* @param size 看上去是支持64位int,本質上只支持int類型
* @return
**/
func makechan64(t *chantype, size int64) *hchan {
if int64(int(size)) != size { // 說明有溢出
panic(plainError("makechan: size out of range"))
}
return makechan(t, int(size))
}
/**
* 創建通道
* @param t 通道類型指針
* @param size 通道大小,0表示無緩衝通道
* @return
**/
func makechan(t *chantype, size int) *hchan {
elem := t.elem
// compiler checks this but be safe.
// 編譯器對此進行檢查,但很安全。
if elem.size >= 1<<16 {
throw("makechan: invalid channel element type")
}
// 非8字節對齊
if hchanSize%maxAlign != 0 || elem.align > maxAlign {
throw("makechan: bad alignment")
}
// MulUintptr返回a * b以及乘法是否溢出。 在受支持的平臺上,這是編譯器固有的功能。
mem, overflow := math.MulUintptr(elem.size, uintptr(size))
// 發生溢出,或者分配內存超限制,或者size<0
if overflow || mem > maxAlloc-hchanSize || size < 0 {
panic(plainError("makechan: size out of range"))
}
// Hchan does not contain pointers interesting for GC when elements stored in buf do not contain pointers.
// buf points into the same allocation, elemtype is persistent.
// SudoG's are referenced from their owning thread so they can't be collected.
// TODO(dvyukov,rlh): Rethink when collector can move allocated objects.
// 當存儲在buf中的元素不包含指針時,Hchan不包含GC感興趣的指針。 buf指向相同的分配,
// elemtype是持久的。 SudoG是從它們自己的線程中引用的,因此無法收集它們。
// TODO(dvyukov,rlh):重新考慮何時收集器可以移動分配的對象。
var c *hchan
switch {
case mem == 0: // 不需要分配置內存空間
// Queue or element size is zero.
// 隊列或元素大小爲零。
// mallocgc分配一個大小爲size字節的對象。 小對象是從每個P緩存的空閒列表中分配的。
// 大對象(> 32 kB)直接從堆中分配。
c = (*hchan)(mallocgc(hchanSize, nil, true))
// Race detector uses this location for synchronization.
// 競態探測器使用此位置進行同步。
c.buf = c.raceaddr()
case elem.ptrdata == 0: // 無指針數據
// Elements do not contain pointers.
// Allocate hchan and buf in one call.
// 元素不包含指針。 在一次調用中分配hchan和buf。
c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
c.buf = add(unsafe.Pointer(c), hchanSize) // 修改數據指針地址
default:
// Elements contain pointers.
// 元素包含指針。
c = new(hchan)
c.buf = mallocgc(mem, elem, true)
}
c.elemsize = uint16(elem.size) // 設置元素大小
c.elemtype = elem // 設置元素類型
c.dataqsiz = uint(size) // 設置通道大小
if debugChan {
print("makechan: chan=", c, "; elemsize=", elem.size, "; dataqsiz=", size, "\n")
}
return c
}
// chanbuf(c, i) is pointer to the i'th slot in the buffer.
/**
* chanbuf(c, i) 返回指向緩衝區中第i個槽值的指針。
* @param c 通道對象
* @return i 第i個槽位
**/
func chanbuf(c *hchan, i uint) unsafe.Pointer {
return add(c.buf, uintptr(i)*uintptr(c.elemsize))
}
// entry point for c <- x from compiled code
//go:nosplit
/**
* 編譯代碼中c <- x的入口點,即當我們編寫代碼 c <- x時,就是調用此方法
* @param c 通道對象
* @param elem 需要發送的元素
* @return
**/
func chansend1(c *hchan, elem unsafe.Pointer) {
chansend(c, elem, true, getcallerpc())
}
/*
* generic single channel send/recv
* If block is not nil,
* then the protocol will not
* sleep but return if it could
* not complete.
*
* sleep can wake up with g.param == nil
* when a channel involved in the sleep has
* been closed. it is easiest to loop and re-run
* the operation; we'll see that it's now closed.
*/
/**
* 通用單通道發送/接收
* 如果block不爲nil,則協議將不會休眠,但如果無法完成則返回。
*
* 當涉及休眠的通道已關閉時,可以使用g.param == nil喚醒休眠。
* 最容易循環並重新運行該操作; 我們將看到它現已關閉。
* @param c 通道對象
* @param ep 元素指針
* @param block 是否阻塞
* @param callerpc 調用者指針
* @return bool true:表示發送成功
**/
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
if c == nil { // 通道已爲空
if !block { // 非阻塞
return false
}
// 將當前goroutine置於等待狀態並調用unlockf。 如果unlockf返回false,則繼續執行goroutine程序。
// unlockf一定不能訪問此G的堆棧,因爲它可能在調用gopark和調用unlockf之間移動。
// waitReason參數說明了goroutine已停止的原因。
// 它顯示在堆棧跟蹤和堆轉儲中。
// waitReason應具有唯一性和描述性。
// 不要重複使用waitReason,請添加新的waitReason。
// 更詳細的說明參見:runtime/proc.go和runtime/runtime2.go
gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2)
throw("unreachable")
}
if debugChan { // 當前已經爲false
print("chansend: chan=", c, "\n")
}
if raceenabled { // 當前已經爲false
racereadpc(c.raceaddr(), callerpc, funcPC(chansend))
}
// Fast path: check for failed non-blocking operation without acquiring the lock.
//
// After observing that the channel is not closed, we observe that the channel is
// not ready for sending. Each of these observations is a single word-sized read
// (first c.closed and second c.recvq.first or c.qcount depending on kind of channel).
// Because a closed channel cannot transition from 'ready for sending' to
// 'not ready for sending', even if the channel is closed between the two observations,
// they imply a moment between the two when the channel was both not yet closed
// and not ready for sending. We behave as if we observed the channel at that moment,
// and report that the send cannot proceed.
//
// It is okay if the reads are reordered here: if we observe that the channel is not
// ready for sending and then observe that it is not closed, that implies that the
// channel wasn't closed during the first observation.
// 快速路徑:在沒有獲取鎖的情況下檢查失敗的非阻塞操作。
//
// 觀察到通道未關閉後,我們觀察到該通道尚未準備好發送。 這些觀察中的每一個都是單個字(word)大小的讀取
// (根據通道的類型,取第一個c.closed和第二個c.recvq.first或c.qcount)。
// 因爲關閉的通道無法從“準備發送”轉換爲“未準備發送”,所以即使通道在兩個觀測值之間處於關閉狀態,
// 它們也隱含着兩者之間的一個時刻,即通道既未關閉又未關閉準備發送。 我們的行爲就好像我們當時在觀察該通道,
// 並報告發送無法繼續進行。
//
// 如果在此處對讀取進行了重新排序,也是可以的:如果我們觀察到該通道尚未準備好發送,然後觀察到它沒有關閉,
// 則意味着該通道在第一次觀察期間沒有關閉。
// !block:非阻塞狀態
// c.closed == 0:通道未關閉
// (c.dataqsiz == 0 && c.recvq.first == nil):通道中沒有數據,並且沒有接收者
// (c.dataqsiz > 0 && c.qcount == c.dataqsiz):通道中有數據,並且已經滿了
if !block && c.closed == 0 && ((c.dataqsiz == 0 && c.recvq.first == nil) ||
(c.dataqsiz > 0 && c.qcount == c.dataqsiz)) {
return false
}
var t0 int64
if blockprofilerate > 0 {
t0 = cputicks()
}
// 加鎖
lock(&c.lock)
if c.closed != 0 { // 通道已經關閉,解鎖,拋出panic
unlock(&c.lock)
panic(plainError("send on closed channel"))
}
if sg := c.recvq.dequeue(); sg != nil {
// Found a waiting receiver. We pass the value we want to send
// directly to the receiver, bypassing the channel buffer (if any).
// 找到了等待的接收者。 我們繞過通道緩衝區(如果有)將要發送的值直接發送給接收器。
send(c, sg, ep, func() { unlock(&c.lock) }, 3)
return true
}
// 沒有找到接收者,並且隊列元素未填滿通道的循環隊列
if c.qcount < c.dataqsiz {
// Space is available in the channel buffer. Enqueue the element to send.
// 通道緩衝區中有可用空間。 使要發送的元素入隊。
qp := chanbuf(c, c.sendx)
if raceenabled { // 此值已經爲false
raceacquire(qp)
racerelease(qp)
}
// 執行內存移動
typedmemmove(c.elemtype, qp, ep)
c.sendx++ // 指向下一個可以發送數據的空位
if c.sendx == c.dataqsiz { // 已經達到了末尾
c.sendx = 0 // 重新指向頭部
}
c.qcount++ // 通道總的數據加1
unlock(&c.lock) // 解鎖
return true // 說明發送成功
}
// 非阻塞,因爲找不到接收者,所以失敗
if !block {
unlock(&c.lock)
return false
}
// Block on the channel. Some receiver will complete our operation for us.
// 在通道上阻塞。一些接收器將爲我們完成操作。
// getg將返回指向當前g的指針。獲取suodg
gp := getg()
mysg := acquireSudog()
mysg.releasetime = 0
if t0 != 0 {
mysg.releasetime = -1
}
// No stack splits between assigning elem and enqueuing mysg
// on gp.waiting where copystack can find it.
// 在分配elem和將mysg入隊到gp.waitcopy可以找到它的地方之間沒有堆棧拆分。
mysg.elem = ep
mysg.waitlink = nil
mysg.g = gp
mysg.isSelect = false
mysg.c = c
gp.waiting = mysg
gp.param = nil
c.sendq.enqueue(mysg)
gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2)
// Ensure the value being sent is kept alive until the
// receiver copies it out. The sudog has a pointer to the
// stack object, but sudogs aren't considered as roots of the
// stack tracer.
// 確保發送的值保持活動狀態,直到接收者將其複製出來。
// sudog具有指向堆棧對象的指針,但是sudog不被視爲堆棧跟蹤器的根。
KeepAlive(ep)
// someone woke us up.
// 有人把我們喚醒了
if mysg != gp.waiting {
throw("G waiting list is corrupted")
}
gp.waiting = nil
gp.activeStackChans = false
if gp.param == nil {
if c.closed == 0 {
throw("chansend: spurious wakeup")
}
panic(plainError("send on closed channel"))
}
gp.param = nil
if mysg.releasetime > 0 {
blockevent(mysg.releasetime-t0, 2)
}
mysg.c = nil
releaseSudog(mysg) // 釋放sudog
return true
}
// send processes a send operation on an empty channel c.
// The value ep sent by the sender is copied to the receiver sg.
// The receiver is then woken up to go on its merry way.
// Channel c must be empty and locked. send unlocks c with unlockf.
// sg must already be dequeued from c.
// ep must be non-nil and point to the heap or the caller's stack.
/**
* send在空通道c上執行發送操作。
* 將發送方發送的ep值複製到接收方sg。
* 然後將接收器喚醒,繼續前進。
* 頻道c必須爲空且已鎖定。 使用unlockf發送解鎖通道c。
* sg必須已經從c中出隊。
* ep必須爲非nil,並指向堆或調用者的堆棧。
* @param c 通道對象
* @param sg
* @param ep 元素指針
* @param unlockf 角鎖方法
* @param skip
* @return
**/
func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
if raceenabled { // 此值已經爲false
if c.dataqsiz == 0 {
racesync(c, sg)
} else {
// Pretend we go through the buffer, even though
// we copy directly. Note that we need to increment
// the head/tail locations only when raceenabled.
qp := chanbuf(c, c.recvx)
raceacquire(qp)
racerelease(qp)
raceacquireg(sg.g, qp)
racereleaseg(sg.g, qp)
c.recvx++
if c.recvx == c.dataqsiz {
c.recvx = 0
}
c.sendx = c.recvx // c.sendx = (c.sendx+1) % c.dataqsiz
}
}
if sg.elem != nil { // 元素不爲空,直接發送
sendDirect(c.elemtype, sg, ep)
sg.elem = nil
}
gp := sg.g
unlockf()
gp.param = unsafe.Pointer(sg)
if sg.releasetime != 0 {
sg.releasetime = cputicks()
}
goready(gp, skip+1) // Question: 這裏是用來做什麼?
}
// Sends and receives on unbuffered or empty-buffered channels are the
// only operations where one running goroutine writes to the stack of
// another running goroutine. The GC assumes that stack writes only
// happen when the goroutine is running and are only done by that
// goroutine. Using a write barrier is sufficient to make up for
// violating that assumption, but the write barrier has to work.
// typedmemmove will call bulkBarrierPreWrite, but the target bytes
// are not in the heap, so that will not help. We arrange to call
// memmove and typeBitsBulkBarrier instead.
/**
* 在一個無緩衝通道或空緩衝通道上發送和接收是一個正在運行的goroutine寫入另一個正在運行的goroutine堆棧的唯一操作。
* GC假定僅在goroutine運行時才發生堆棧寫入,並且僅由該goroutine完成。 使用寫屏障足以彌補違反該假設的缺點,
* 但是寫屏障必須起作用。 typedmemmove將調用bulkBarrierPreWrite,但是目標字節不在堆中,因此這無濟於事。
* 我們安排調用memmove和typeBitsBulkBarrier。
* @param t 元素類型
* @param sg
* @param src 數據指針
* @return
**/
func sendDirect(t *_type, sg *sudog, src unsafe.Pointer) {
// src is on our stack, dst is a slot on another stack.
// src在我們的棧上,dst是另一個棧上的位置。
// Once we read sg.elem out of sg, it will no longer
// be updated if the destination's stack gets copied (shrunk).
// So make sure that no preemption points can happen between read & use.
// 一旦我們從sg中讀取出sg.elem,如果目標堆棧被複制(縮小),它將不再被更新。
// 因此,請確保在讀取和使用之間沒有任何搶佔點。
dst := sg.elem
// 帶屏障的寫操作,參見:runtime/mbitmap.go/typeBitsBulkBarrier方法
typeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.size)
// No need for cgo write barrier checks because dst is always
// Go memory.
// 不需要cgo寫屏障檢查,因爲dst始終是Go內存。
memmove(dst, src, t.size)
}
/**
* 直接接收通道數據
* @param
* @return
**/
func recvDirect(t *_type, sg *sudog, dst unsafe.Pointer) {
// dst is on our stack or the heap, src is on another stack.
// The channel is locked, so src will not move during this
// operation.
// dst在我們的棧或堆上,src在另一個棧上。 通道已鎖定,因此src在此操作期間將不會移動。
src := sg.elem
typeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.size)
memmove(dst, src, t.size)
}
/**
* 關閉通道
* @param
* @return
**/
func closechan(c *hchan) {
if c == nil { // 通道爲nil
panic(plainError("close of nil channel"))
}
lock(&c.lock) // 鎖定
if c.closed != 0 { // 通道已經關閉,再關閉就報panic
unlock(&c.lock) // 解鎖
panic(plainError("close of closed channel"))
}
if raceenabled { // 此值已經爲false
callerpc := getcallerpc()
racewritepc(c.raceaddr(), callerpc, funcPC(closechan))
racerelease(c.raceaddr())
}
c.closed = 1 // 標記通道已經關閉
var glist gList
// release all readers // 釋放所有的接收者
for {
sg := c.recvq.dequeue() // 接收者隊列
if sg == nil { // 隊列爲空
break
}
if sg.elem != nil {
typedmemclr(c.elemtype, sg.elem) // 進行內存清理
sg.elem = nil
}
if sg.releasetime != 0 {
sg.releasetime = cputicks()
}
gp := sg.g
gp.param = nil // 參數清零
if raceenabled {
raceacquireg(gp, c.raceaddr())
}
glist.push(gp) // gp入隊頭
}
// release all writers (they will panic)
// 釋放所有寫對象(他們會恐慌)
for {
sg := c.sendq.dequeue() // 發送者隊列
if sg == nil { // 隊列爲空
break
}
sg.elem = nil // 直接清空內存?這麼做不會內存溢出?
if sg.releasetime != 0 {
sg.releasetime = cputicks()
}
gp := sg.g
gp.param = nil // 參數清零
if raceenabled {
raceacquireg(gp, c.raceaddr())
}
glist.push(gp) // gp入隊頭
}
unlock(&c.lock)
// Ready all Gs now that we've dropped the channel lock.
// 現在我們已經釋放了通道鎖,準備好所有G。
for !glist.empty() { // 對所有的g,將schedlink清0,並且設置已經已經準備好
gp := glist.pop()
gp.schedlink = 0
goready(gp, 3)
}
}
// entry points for <- c from compiled code
//go:nosplit
/**
* 編譯代碼中 <-c 的入口點
* @param
* @return
**/
func chanrecv1(c *hchan, elem unsafe.Pointer) {
chanrecv(c, elem, true)
}
//go:nosplit
/**
*
* @param
* @return received true:表示已經接收到
**/
func chanrecv2(c *hchan, elem unsafe.Pointer) (received bool) {
_, received = chanrecv(c, elem, true)
return
}
// chanrecv receives on channel c and writes the received data to ep.
// ep may be nil, in which case received data is ignored.
// If block == false and no elements are available, returns (false, false).
// Otherwise, if c is closed, zeros *ep and returns (true, false).
// Otherwise, fills in *ep with an element and returns (true, true).
// A non-nil ep must point to the heap or the caller's stack.
/**
* chanrecv在通道c上接收並將接收到的數據寫入ep。
* ep可能爲nil,在這種情況下,接收到的數據將被忽略。
* 如果block == false並且沒有可用元素,則返回(false,false)。
* 否則,如果c關閉,則* ep爲零並返回(true,false)。
* 否則,用一個元素填充* ep並返回(true,true)。
* 非nil必須指向堆或調用者的堆棧。
* @param c 通道對象
* @param ep 用於接收數據的指針
* @param block true: 表示阻塞
* @return selected true表示被選擇
* @return received true表示已經接收到值
**/
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
// raceenabled: don't need to check ep, as it is always on the stack
// or is new memory allocated by reflect.
// raceenabled:不需要檢查ep,因爲它始終在堆棧中,或者是反射所分配的新內存。
if debugChan { // 此值已經爲false
print("chanrecv: chan=", c, "\n")
}
if c == nil { // 通道爲空
if !block { // 非阻塞
return
}
// 阻塞狀態
// 將當前goroutine置於等待狀態並調用unlockf。
gopark(nil, nil, waitReasonChanReceiveNilChan, traceEvGoStop, 2)
throw("unreachable")
}
// Fast path: check for failed non-blocking operation without acquiring the lock.
//
// After observing that the channel is not ready for receiving, we observe that the
// channel is not closed. Each of these observations is a single word-sized read
// (first c.sendq.first or c.qcount, and second c.closed).
// Because a channel cannot be reopened, the later observation of the channel
// being not closed implies that it was also not closed at the moment of the
// first observation. We behave as if we observed the channel at that moment
// and report that the receive cannot proceed.
//
// The order of operations is important here: reversing the operations can lead to
// incorrect behavior when racing with a close.
// 快速路徑:不獲取鎖定而檢查失敗的非阻塞操作。
//
// 在觀察到通道尚未準備好接收之後,我們觀察到通道未關閉。 這些觀察中的每一個都是單個字(word)大小的讀取
// (第一個c.sendq.first或c.qcount,第二個c.closed)。
// 由於無法重新打開通道,因此對通道未關閉的後續觀察意味着它在第一次觀察時也未關閉。
// 我們的行爲就好像我們當時在觀察該通道,並報告接收無法繼續進行。
//
// 操作順序在這裏很重要:在進行搶佔關閉時,反轉操作可能導致錯誤的行爲。
// !block : 非阻塞
// c.dataqsiz == 0 && c.sendq.first == nil : 通道沒有空間,並且沒有發送者
// c.dataqsiz > 0 && atomic.Loaduint(&c.qcount) == 0 : 通道中有空間,並且沒有數據
// atomic.Load(&c.closed) : 通道未關閉
if !block && (c.dataqsiz == 0 && c.sendq.first == nil ||
c.dataqsiz > 0 && atomic.Loaduint(&c.qcount) == 0) &&
atomic.Load(&c.closed) == 0 {
return
}
var t0 int64
if blockprofilerate > 0 {
t0 = cputicks()
}
lock(&c.lock) // 加鎖
if c.closed != 0 && c.qcount == 0 { // 通道已經關閉,並且通道中沒有數據
if raceenabled {
raceacquire(c.raceaddr())
}
unlock(&c.lock) // 解鎖
if ep != nil {
typedmemclr(c.elemtype, ep) // 進行內存清理
}
return true, false
}
if sg := c.sendq.dequeue(); sg != nil { // 發送者隊例不爲空
// Found a waiting sender. If buffer is size 0, receive value
// directly from sender. Otherwise, receive from head of queue
// and add sender's value to the tail of the queue (both map to
// the same buffer slot because the queue is full).
// 找到了等待發送者。 如果緩衝區的大小爲0,則直接從發送方接收值。
// 否則,從隊列的開頭接收並將發件人的值添加到隊列的末尾(由於隊列已滿,因此兩者都映射到同一緩衝區)。
recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
return true, true
}
// 沒有發送者,數據隊列不爲空
if c.qcount > 0 {
// Receive directly from queue
// 直接從數據隊列中接收數據
qp := chanbuf(c, c.recvx)
if raceenabled {未
raceacquire(qp)
racerelease(qp)
}
if ep != nil { // 進行內存數據移動
typedmemmove(c.elemtype, ep, qp)
}
typedmemclr(c.elemtype, qp) // 清理qp的c.elemtype類型內存數據
c.recvx++ // 指向下一個接收位置
if c.recvx == c.dataqsiz { // 說明已經指向了隊列末尾了的下一個位置了
c.recvx = 0 // 重新指向頭部
}
c.qcount-- // 數據隊列中的數據減少一個
unlock(&c.lock) // 解鎖
return true, true
}
// 沒有發送者,並且隊列爲空,並且是非阻塞狀態
if !block {
unlock(&c.lock)
return false, false
}
// no sender available: block on this channel.
// 沒有可用的發送者:在此通道阻塞。
gp := getg()
mysg := acquireSudog()
mysg.releasetime = 0
if t0 != 0 {
mysg.releasetime = -1
}
// No stack splits between assigning elem and enqueuing mysg
// on gp.waiting where copystack can find it.
// 在分配elem和將mysg入隊到gp.waitcopy可以找到它的地方之間沒有堆棧拆分。
mysg.elem = ep
mysg.waitlink = nil
gp.waiting = mysg
mysg.g = gp
mysg.isSelect = false
mysg.c = c
gp.param = nil
c.recvq.enqueue(mysg)
// 阻塞狀態
// 將當前goroutine置於等待狀態並調用unlockf。
gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanReceive, traceEvGoBlockRecv, 2)
// someone woke us up
// 有人把我們喚醒了
if mysg != gp.waiting {
throw("G waiting list is corrupted")
}
gp.waiting = nil
gp.activeStackChans = false
if mysg.releasetime > 0 {
blockevent(mysg.releasetime-t0, 2)
}
closed := gp.param == nil
gp.param = nil
mysg.c = nil
releaseSudog(mysg) // 釋放sudog
return true, !closed
}
// recv processes a receive operation on a full channel c.
// There are 2 parts:
// 1) The value sent by the sender sg is put into the channel
// and the sender is woken up to go on its merry way.
// 2) The value received by the receiver (the current G) is
// written to ep.
// For synchronous channels, both values are the same.
// For asynchronous channels, the receiver gets its data from
// the channel buffer and the sender's data is put in the
// channel buffer.
// Channel c must be full and locked. recv unlocks c with unlockf.
// sg must already be dequeued from c.
// A non-nil ep must point to the heap or the caller's stack.
/**
* recv在完整通道c上處理接收操作。
* 有2個部分:
* 1)將發送方sg發送的值放入通道中,並喚醒發送方以繼續進行。
* 2)接收方接收到的值(當前G)被寫入ep。
* 對於同步通道,兩個值相同。
* 對於異步通道,接收者從通道緩衝區獲取數據,而發送者的數據放入通道緩衝區。
* 頻道c必須已滿且已鎖定。 recv用unlockf解鎖c。
* sg必須已經從c中出隊。
* 非nil必須指向堆或調用者的堆棧。
* @param c 通道對象針對
* @param sg
* @param ep 用戶接收元素的指針
* @param unlockf 解鎖函數
* @param skip
* @return
**/
func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
if c.dataqsiz == 0 { // 數據隊列的大小是0,表示這上一個無緩衝的通道
if raceenabled {
racesync(c, sg)
}
if ep != nil { // 元素指針不爲空
// copy data from sender
// 直接從發送發拷貝數據
recvDirect(c.elemtype, sg, ep)
}
} else { // 有緩衝通道
// Queue is full. Take the item at the
// head of the queue. Make the sender enqueue
// its item at the tail of the queue. Since the
// queue is full, those are both the same slot.
// 隊列已滿。 將item放在隊列的開頭。 使發送者將其item排入隊列的末尾。
// 由於隊列已滿,因此它們都是相同的槽位。
qp := chanbuf(c, c.recvx)
if raceenabled { // 此值已經爲false
raceacquire(qp)
racerelease(qp)
raceacquireg(sg.g, qp)
racereleaseg(sg.g, qp)
}
// copy data from queue to receiver
if ep != nil {
typedmemmove(c.elemtype, ep, qp)
}
// copy data from sender to queue
// 將數據從隊列複製到接收者
typedmemmove(c.elemtype, qp, sg.elem)
c.recvx++ // 指向下一個接收位置
if c.recvx == c.dataqsiz { // 已經達到了末尾的下一個位置,需要重新指向頭部
c.recvx = 0
}
c.sendx = c.recvx // c.sendx = (c.sendx+1) % c.dataqsiz // 發送者位置前移
}
sg.elem = nil
gp := sg.g
unlockf() // 解鎖
gp.param = unsafe.Pointer(sg)
if sg.releasetime != 0 {
sg.releasetime = cputicks()
}
// 將當前goroutine置於等待狀態並解鎖。 可以通過調用goready(gp)使goroutine重新運行
goready(gp, skip+1) // 標記go
}
/**
*
* @param
* @return
**/
func chanparkcommit(gp *g, chanLock unsafe.Pointer) bool {
// There are unlocked sudogs that point into gp's stack. Stack
// copying must lock the channels of those sudogs.
// 有未鎖定的sudog指向gp的堆棧。堆棧複製必須鎖定那些sudog的通道。
// activeStackChans指示存在指向該goroutine堆棧的未鎖定通道。
// 如果爲true,則堆棧複製需要獲取通道鎖以保護堆棧的這些區域。
gp.activeStackChans = true
unlock((*mutex)(chanLock)) // 解鎖
return true
}
// compiler implements
//
// select {
// case c <- v:
// ... foo
// default:
// ... bar
// }
//
// as
//
// if selectnbsend(c, v) {
// ... foo
// } else {
// ... bar
// }
//
/**
* 編譯器實現,將goroutine的select send(case c <- v)語句轉成對應的方法執行
* @param
* @return
**/
func selectnbsend(c *hchan, elem unsafe.Pointer) (selected bool) {
return chansend(c, elem, false, getcallerpc())
}
// compiler implements
//
// select {
// case v = <-c:
// ... foo
// default:
// ... bar
// }
//
// as
//
// if selectnbrecv(&v, c) {
// ... foo
// } else {
// ... bar
// }
//
/**
* 編譯器實現,將goroutine的select receive(v = <-c)語句轉成對應的方法執行
* @param
* @return
**/
func selectnbrecv(elem unsafe.Pointer, c *hchan) (selected bool) {
selected, _ = chanrecv(c, elem, false)
return
}
// compiler implements
//
// select {
// case v, ok = <-c:
// ... foo
// default:
// ... bar
// }
//
// as
//
// if c != nil && selectnbrecv2(&v, &ok, c) {
// ... foo
// } else {
// ... bar
// }
//
/**
* 編譯器實現,將goroutine的select receive(case v, ok = <-c:)語句轉成對應的方法執行
* @param
* @return
**/
func selectnbrecv2(elem unsafe.Pointer, received *bool, c *hchan) (selected bool) {
// TODO(khr): just return 2 values from this function, now that it is in Go.
// TODO(khr):此函數位於Go中,只需返回2個值即可。表示這是一個歷史遺留
selected, *received = chanrecv(c, elem, false)
return
}
//go:linkname reflect_chansend reflect.chansend
/**
*
* go:linkname引導編譯器將當前(私有)方法或者變量在編譯時鏈接到指定的位置的方法或者變量,
* 第一個參數表示當前方法或變量,第二個參數表示目標方法或變量,因爲這關指令會破壞系統和包的模塊化,
* 因此在使用時必須導入unsafe
* 參見:https://blog.csdn.net/lastsweetop/article/details/78830772
* @param
* @return
**/
func reflect_chansend(c *hchan, elem unsafe.Pointer, nb bool) (selected bool) {
return chansend(c, elem, !nb, getcallerpc())
}
//go:linkname reflect_chanrecv reflect.chanrecv
func reflect_chanrecv(c *hchan, nb bool, elem unsafe.Pointer) (selected bool, received bool) {
return chanrecv(c, elem, !nb)
}
//go:linkname reflect_chanlen reflect.chanlen
func reflect_chanlen(c *hchan) int {
if c == nil {
return 0
}
return int(c.qcount)
}
//go:linkname reflectlite_chanlen internal/reflectlite.chanlen
func reflectlite_chanlen(c *hchan) int {
if c == nil {
return 0
}
return int(c.qcount)
}
//go:linkname reflect_chancap reflect.chancap
func reflect_chancap(c *hchan) int {
if c == nil {
return 0
}
return int(c.dataqsiz)
}
//go:linkname reflect_chanclose reflect.chanclose
func reflect_chanclose(c *hchan) {
closechan(c)
}
/**
* 入隊操作
* @param sudog 需要入隊的元素
* @return
**/
func (q *waitq) enqueue(sgp *sudog) {
sgp.next = nil
x := q.last
if x == nil { // 隊列中沒有元素
sgp.prev = nil
q.first = sgp
q.last = sgp
return
}
// 隊列中已經有元素
sgp.prev = x
x.next = sgp
q.last = sgp
}
/**
* 出隊
* @param
* @return
**/
func (q *waitq) dequeue() *sudog {
for {
sgp := q.first
if sgp == nil { // 隊列中沒有元素
return nil
}
y := sgp.next
if y == nil { // 隊列中只有一個元素
q.first = nil
q.last = nil
} else {
y.prev = nil
q.first = y
sgp.next = nil // mark as removed (see dequeueSudog) // 標記爲已刪除(請參閱dequeueSudog)
}
// if a goroutine was put on this queue because of a
// select, there is a small window between the goroutine
// being woken up by a different case and it grabbing the
// channel locks. Once it has the lock
// it removes itself from the queue, so we won't see it after that.
// We use a flag in the G struct to tell us when someone
// else has won the race to signal this goroutine but the goroutine
// hasn't removed itself from the queue yet.
// 如果由於選擇而將goroutine放在此隊列中,則在其他情況下喚醒goroutine並獲取通道鎖之間會有一個小窗口。
// 一旦擁有了鎖,它就會將自己從隊列中刪除,因此之後我們將看不到它。
// 我們在G結構中使用一個標誌來告訴我們何時其他人贏得了發信號通知此goroutine的競賽,
// 但goroutine尚未將自己從隊列中刪除。
if sgp.isSelect && !atomic.Cas(&sgp.g.selectDone, 0, 1) {
continue
}
return sgp
}
}
func (c *hchan) raceaddr() unsafe.Pointer {
// Treat read-like and write-like operations on the channel to
// happen at this address. Avoid using the address of qcount
// or dataqsiz, because the len() and cap() builtins read
// those addresses, and we don't want them racing with
// operations like close().
// 將通道上的讀取和寫入操作視爲在此地址發生。 避免使用qcount或dataqsiz的地址,
// 因爲內置的len()和cap()會讀取這些地址,並且我們不希望它們與close()之類的操作競爭。
return unsafe.Pointer(&c.buf)
}
/**
* 這個方法現在看來沒有什麼用了
* @param
* @return
**/
func racesync(c *hchan, sg *sudog) {
racerelease(chanbuf(c, 0))
raceacquireg(sg.g, chanbuf(c, 0))
racereleaseg(sg.g, chanbuf(c, 0))
raceacquire(chanbuf(c, 0))
}