徹底搞懂 channel 原理 -一-
圖片拍攝於 2021 年 10 月 3 日,我的信仰。
躺的太久,該起牀了。
寧可我卷死別人,不能讓別人卷我。
之前斷斷續續看過Go
幾個模塊的源碼,可從未下筆,導致有些細節記不起來了。打算寫一系列文章重新記錄。
channel
源碼解析的文章太多了。一篇文章的長篇大論大部分人沒耐心看完,所以我打算分開寫,最後附上完整的ppt
。
當然這其中不會涉及過多細節源碼,因爲有時候,細節是魔鬼。
介紹
channel
一些基礎介紹這裏就不過多涉及了,都 1202 年了,我不相信用過Go
的人沒用過channel
。
當然下圖也涵蓋了大部分使用姿勢。
有一道使用channel
進行任務編排的經典的題。題目如下:
有四個goroutine
,編號爲 1、2、3、4。每秒鐘會有一個 goroutine
打印自己的編號。請你實現這個程序,讓輸出的編號總是按照 1、2、3、4、1、2、3、4、…… 的順序打印出來。就像這樣,
可以自己先思考下,代碼也可以通過後臺回覆擊鼓傳花
獲取。
原理解析
從一個簡單的例子說起。
創建一個main.go
文件,代碼如下,
我們來看看這段代碼編譯以後長啥樣。
想得到go
程序的彙編代碼並不難。
可以使用go tool compile -N -l -S main.go
生成彙編代碼:
或者使用go tool compile -N -l main.go
先編譯出代碼,然後再使用go tool objdump main.o
反彙編出代碼。
還可以通過go build -gcflags -S main.go
同樣可以得到彙編的代碼。
上面兩種我就不演示了,可以自行實驗。他們之中flag
的具體含義也可以自行了解。
如果你覺得上面要自己敲代碼比較麻煩,我推薦一個更加直接可視化的工具。
綜上,從編譯的代碼我們可以看出,上述初始化一個channel
,
ch := make(chan struct{})
實際上調用的是runtime.makechan
。
runtime.hchan
的指針。
runtime.hchan
結構。
我們先來解釋hchan
結構體各個字段的含義,之後在案例介紹中會更加詳細的說明他們的作用。
先來看qcount
和dataqsiz
有什麼區別?
你去銀行辦事,銀行有 5 個辦事窗口,那麼dataqsiz
就等於 5。在這裏體現的是channel
的容量爲 5。去銀行的時候,當前有 3 個窗口有人正在辦事,那麼qcount
就等於 3,體現channel
當前有 3 個數據元素。那麼此時銀行還可以再接待 2 個客戶,對應還可以往channel
發送 2 個數據元素。
其他字段現在看看說明就行了,後面會細講。
到這裏我們就知道創建一個channel
本質上就是得到一個runtime.hchan
的指針,後續對此chan
的操作,無非就是對結構體字段進行相對應的操作。
同時我們也能猜出,爲啥channel
能在不同的 g 中傳遞消息,而對於使用者來說不用擔心併發的問題。
其實就是hchan
內部使用互斥鎖來保證了併發安全。
最後我們來看一下runtime.makechan
函數核心實現,當然註釋已經很明白了。
可以看到創建的時候有一段switch
分支代碼,那麼什麼情況下會走對應的case
呢?
根據上面的信息,我們可以得出,
-
如果創建一個無緩衝
channel
,那麼只需要爲runtime.hchan
本身分配一段內存空間即可。 -
如果創建的緩衝
channel
存儲的類型不是指針類型,會爲當前channel
和存儲類型元素的緩衝區,分配一塊連續的內存空間。 -
在默認情況下 (緩衝
channel
存儲類型包含指針),會單獨爲runtime.hchan
和緩衝區分配內存。
總結
這篇我們主要介紹瞭如何獲取 go 程序的彙編代碼,通過彙編代碼知道創建channel
的具體函數runtime.makechan
。
同時我們還知道不同的創建姿勢會導致走向不同的內存空間分配邏輯。
最後通過創建函數我們知道channel
在程序運行時是使用runtime.hchan
來表示。
下一篇我們繼續。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/5WpCntMFUcWZ2N6_AsnciQ