關於對channel的理解,我們先看看channel的資料結構是怎樣的吧
type hchan struct {
2. qcount uint // 當前佇列中剩餘元素個數
3. dataqsiz uint // 環形佇列長度,即可以存放的元素個數
4. buf unsafe.Pointer // 環形佇列指標
5. elemsize uint16// 每個元素的大小
6. closed uint32 // 標識關閉狀態
7. elemtype *_type // 元素型別
8. sendx uint // 佇列下標,指示元素寫入時存放到佇列中的位置
9. recvx uint // 佇列下標,指示元素從佇列的該位置讀出
10. recvq waitq // 等待讀訊息的goroutine佇列
11. sendq waitq // 等待寫訊息的goroutine佇列
12. lock mutex // 互斥鎖,chan不允許併發讀寫 13.
}
從上面的channel資料結構可以看出channel由佇列、型別資訊、goroutine等待佇列組成。
可以看出chan內部實現了一個環形佇列作為其緩衝區,佇列的長度是建立chan時指定的。
看一下上面這張圖可以這樣理解:
- dataqsiz指定了佇列長度為6,說明chan可快取6個元素;
- buf指向佇列的記憶體,佇列中還剩下兩個元素;
- qcount表示佇列中還有兩個元素;
- sendx指示後續寫入的資料儲存的位置,取值(0,6);
- recvx指示從該位置讀取資料, 取值[0, 6);
channel會在以下情況將goroutine堵塞:
- 從channel讀資料,如果channel緩衝區為空或者沒有緩衝區,當前goroutine會被阻塞。
- 向channel寫資料,如果channel緩衝區已滿或者沒有緩衝區,當前goroutine會被阻塞。
- 被阻塞的goroutine將會掛在channel的等待佇列中
被channel堵塞的goroutine如何喚醒:
- 因讀阻塞的goroutine會被向channel寫入資料的goroutine喚醒;
- 因寫阻塞的goroutine會被從channel讀資料的goroutine喚醒;
- 上圖展示了一個沒有緩衝區的channel,有幾個goroutine阻塞等待讀資料
- 需要特別注意的是一般情況下recvq和sendq至少有一個為空。只有一個例外,那就是同一個goroutine使用select語句向 channel一邊寫資料,一邊讀資料
channel的型別限制
- 一個channel只能傳遞一種型別的值,型別資訊儲存在hchan資料結構中。
- elemtype代表型別,用於資料傳遞過程中的賦值;
- elemsize代表型別大小,用於在buf中定位元素位置;
channel鎖機制
- 這個很好理解,一個channel同時僅允許被一個goroutine讀寫
建立channel的過程實際上是初始化hchan結構。其中型別資訊和緩衝區長度由make語句傳入,buf的大小則與元素大小和緩衝區長度共同決定,以下程式碼演示瞭如何建立一個偽channel:
func makechan(t *chantype, size int) *hchan {
2. var c *hchan
3. c = new(hchan)
4. c.buf = malloc(元素型別大小*size)
5. c.elemsize = 元素型別大小
6. c.elemtype = 元素型別
7. c.dataqsiz = size
8. return c}
如何向channel寫資料:
- 等待接收佇列recvq不為空,說明緩衝區中沒有資料或者沒有緩衝區,此時直接從recvq取出G,並把資料寫入,最後把該G喚醒,結束髮送過程;
- 如果緩衝區中有空餘位置,將資料寫入緩衝區,結束髮送過程;
- 如果緩衝區中沒有空餘位置,將待傳送資料寫入G,將當前G加入sendq,進入睡眠,等待被讀goroutine喚醒;
- 上面的圖很好的演示了channel寫資料的過程,不理解的可以多看幾遍
如何從channel中讀資料
- 等待傳送佇列sendq不為空,且沒有緩衝區,直接從sendq中取出G,把G中資料讀出,最後把G喚醒,結束讀取過程;
- 等待傳送佇列sendq不為空,此時說明緩衝區已滿,從緩衝區中首部讀出資料,把G中資料寫入緩衝區尾部,把G喚醒,結束讀取過程;
- 如果緩衝區中有資料,則從緩衝區取出資料,結束讀取過程;
- 將當前goroutine加入recvq,進入睡眠,等待被寫goroutine喚醒;
- 以上圖很好的說明了channel是如何將資料讀取出來的,不懂多看幾遍哈
關閉channel應該避免以下的場景出現:
- 以下幾種場景會出現panic:
- 關閉值為nil的channel
- 關閉已經被關閉的channel
- 向已經關閉的channel寫資料
以上這些是我本人透過查詢資料整理出來關於對channel的理解,如有不足之處還望指點糾正。
本作品採用《CC 協議》,轉載必須註明作者和本文連結