以前知道go團隊在實現channel這種協程間通訊的大殺器時只用了700多行代碼就解決了,因此就去膜拜讀了一把,但以後覆盤總以爲多少有點繞,直到有幸找到一個神級PPT https://speakerdeck.com/kavya... 生動形象的解釋了channel底層是怎麼工做和實現的,因而就帶着這篇PPT再來複盤一遍channel的源碼golang
make(chan task, 3)
初始化channel在調用方有兩種, 一種是帶緩衝的一種是非緩衝的,其初始化的具體實現除了緩衝非緩衝,還分channel的元素是不是指針類型
數據結構
知足send條件下往這個channel發送數據的代碼, 假設當前沒有另外一個goroutine來接收channel的數據學習
G1:spa
for task := range tasks { ch <- task }
當channel滿了以後 c.qcount > c.dataqsiz 若是還有數據發送到該channel
則獲取當前運行的goroutine封裝成sudog,將其插入sendq 隊列並通知系統將當前goroutine中止3d
此時hchan的結構大體長這樣指針
sendq 和 recvq 都是一個由鏈表實現的FIFO隊列code
這裏涉及到三個沒見過的東西協程
1.sudog
sudog 是對當前運行的goroutine和須要發送數據的封裝,有一個前驅指正和後驅指針,hchan的sendq和recvq隊列則是由sudog造成的雙向鏈表blog
2.goparkunlock —> gopark
gopark 將當前goroutine置爲等待狀態
索引
3.goready —> ready
goready 將某個goroutine 喚醒
上面說到,channel容量已滿後, 會阻塞當前goroutine並加到發送隊列中, 那麼何時會釋放這個阻塞的goroutine呢。 以前看channel的學習文章時都說 發送者和接受者必須是成雙成對的 (如今理解爲一個gopark, 一個goready),在下面channel的接收端代碼中能夠看到
由於當從channel中接收數據時, 若是sendq隊列上有等待的的goroutine, 則將它pop出來, 執行接收操做(一下子再講)後調用goready將其喚醒
這裏能夠看到 雖然 golang 有一句名言叫作 「Do not communicate by sharing memory; instead, share memory by communicating.」 告訴咱們用通訊的方式來共享內存而不是用共享內存的方式來通訊,在channel的內部, 接收者和發送者兩個goroutine倒是經過共享hchan來實現通訊的 (可是發送和接收的數據是經過拷貝來傳遞的)。
當hchan 上沒有等待的接收隊列 (recvq) 的狀況下, 往channel 發送數據能夠總結成如下步驟
這裏只列出了當「hchan 的接收者隊列上沒有等待的goroutine」 時這種狀況, 由於在上一句打引號的的狀況中有一種以後須要解釋的騷操做。
channel 的接收實現實質上和發送區別不大, 若是當前沒有阻塞等待發送的goroutine 而且buf中有數據, 則從buf中將當前recvx索引初將須要接收的數據拷貝出來, 而後將其在buf中清除
若是在從channel接收時,發送隊列上有正在阻塞等待的goroutine, 就是上一節中提到的send groutine如何被喚醒的那塊內容, 拷貝 + 喚醒
若是當前無阻塞等待發送數據的goroutine, 而且buf中沒有等待接收的數據, 則同send同樣,將當前的goroutine, 須要接收的數據指針,封裝成sudog插入recvQ隊列尾部, 調用gopark中止當前goroutine
上一節說到, 發送端在接收隊列中無阻塞等待的goroutine時會阻塞並插到sendq隊列中,並留下了一個懸鏈說當接收隊列上有goroutine時會發生一個騷操做。按上面的代碼來看,這種狀況接受者收到的數據也應該是從sendq中取出發送方的sudog並將其發送的值拷貝出來,可是在channel的實現中,當往一個 」空buf(或者非緩衝)可是接收者隊列上有阻塞goroutine的」 channel發送數據時, 發送方會直接把數據寫到接收隊列中那個等待接收的goroutine中。比起等接收者從buf中拷貝數據或者從sendq隊列中pop出sudog再拷貝數據,這樣作少了一次拷貝的過程
帶着這篇PPT來看channel的源碼感受一切都一目瞭然了, 反正這篇PPT必定要看,並且裏面還包含了channel在阻塞goroutine時 go調度器運行狀態的描述。