Golang channel 源碼分析

以前知道go團隊在實現channel這種協程間通訊的大殺器時只用了700多行代碼就解決了,因此就去膜拜讀了一把,但以後覆盤總以爲多少有點繞,直到有幸找到一個神級PPT https://speakerdeck.com/kavya... 生動形象的解釋了channel底層是怎麼工做和實現的,因而就帶着這篇PPT再來複盤一遍channel的源碼golang

Hchan 數據結構

clipboard.png

初始化

make(chan task, 3)
clipboard.png
初始化channel在調用方有兩種, 一種是帶緩衝的一種是非緩衝的,其初始化的具體實現除了緩衝非緩衝,還分channel的元素是不是指針類型
clipboard.png數據結構

Send

知足send條件下往這個channel發送數據的代碼, 假設當前沒有另外一個goroutine來接收channel的數據學習

G1:spa

for task := range tasks {
    ch <- task
}

clipboard.png
clipboard.png

Send to a full channel

當channel滿了以後 c.qcount > c.dataqsiz 若是還有數據發送到該channel
則獲取當前運行的goroutine封裝成sudog,將其插入sendq 隊列並通知系統將當前goroutine中止3d

clipboard.png

clipboard.png

此時hchan的結構大體長這樣指針

clipboard.png
sendq 和 recvq 都是一個由鏈表實現的FIFO隊列code

這裏涉及到三個沒見過的東西協程

1.sudog
sudog 是對當前運行的goroutine和須要發送數據的封裝,有一個前驅指正和後驅指針,hchan的sendq和recvq隊列則是由sudog造成的雙向鏈表blog

clipboard.png

2.goparkunlock —> gopark
gopark 將當前goroutine置爲等待狀態
clipboard.png索引

3.goready —> ready
goready 將某個goroutine 喚醒
clipboard.png

clipboard.png

釋放阻塞的sender goroutine

clipboard.png
上面說到,channel容量已滿後, 會阻塞當前goroutine並加到發送隊列中, 那麼何時會釋放這個阻塞的goroutine呢。 以前看channel的學習文章時都說 發送者和接受者必須是成雙成對的 (如今理解爲一個gopark, 一個goready),在下面channel的接收端代碼中能夠看到
clipboard.png

由於當從channel中接收數據時, 若是sendq隊列上有等待的的goroutine, 則將它pop出來, 執行接收操做(一下子再講)後調用goready將其喚醒
clipboard.png

這裏能夠看到 雖然 golang 有一句名言叫作 「Do not communicate by sharing memory; instead, share memory by communicating.」 告訴咱們用通訊的方式來共享內存而不是用共享內存的方式來通訊,在channel的內部, 接收者和發送者兩個goroutine倒是經過共享hchan來實現通訊的 (可是發送和接收的數據是經過拷貝來傳遞的)。

send channel 小結

當hchan 上沒有等待的接收隊列 (recvq) 的狀況下, 往channel 發送數據能夠總結成如下步驟

  1. hchan 上鎖
  2. 判斷當前hchan 是否有足夠的buf空間
  3. 若是有, 拷貝數據到buf中對應的位置
  4. 若是buf空間不夠,或者初始化的是無緩衝channel, 阻塞當前goroutine並將其封裝成sudog插到sendq中等待被接受者喚醒
  5. hchan 解鎖

這裏只列出了當「hchan 的接收者隊列上沒有等待的goroutine」 時這種狀況, 由於在上一句打引號的的狀況中有一種以後須要解釋的騷操做。

Rcev

channel 的接收實現實質上和發送區別不大, 若是當前沒有阻塞等待發送的goroutine 而且buf中有數據, 則從buf中將當前recvx索引初將須要接收的數據拷貝出來, 而後將其在buf中清除

clipboard.png

Recv from Sender and wakeup Sender

若是在從channel接收時,發送隊列上有正在阻塞等待的goroutine, 就是上一節中提到的send groutine如何被喚醒的那塊內容, 拷貝 + 喚醒

clipboard.png

Recv from empty channel

若是當前無阻塞等待發送數據的goroutine, 而且buf中沒有等待接收的數據, 則同send同樣,將當前的goroutine, 須要接收的數據指針,封裝成sudog插入recvQ隊列尾部, 調用gopark中止當前goroutine

clipboard.png

上一節說到, 發送端在接收隊列中無阻塞等待的goroutine時會阻塞並插到sendq隊列中,並留下了一個懸鏈說當接收隊列上有goroutine時會發生一個騷操做。按上面的代碼來看,這種狀況接受者收到的數據也應該是從sendq中取出發送方的sudog並將其發送的值拷貝出來,可是在channel的實現中,當往一個 」空buf(或者非緩衝)可是接收者隊列上有阻塞goroutine的」 channel發送數據時, 發送方會直接把數據寫到接收隊列中那個等待接收的goroutine中。比起等接收者從buf中拷貝數據或者從sendq隊列中pop出sudog再拷貝數據,這樣作少了一次拷貝的過程

clipboard.png

clipboard.png

clipboard.png

非正常狀況下的sender, recver

未初始化的channel

clipboard.png

往已經關閉的channel發送數據

clipboard.png

從已關閉的channel接受數據

clipboard.png

LAST

帶着這篇PPT來看channel的源碼感受一切都一目瞭然了, 反正這篇PPT必定要看,並且裏面還包含了channel在阻塞goroutine時 go調度器運行狀態的描述。

相關文章
相關標籤/搜索