chanel 使用與原理 一
訪問已經關閉的 chanel
// exp3 用來測試訪問一個已經關閉的且裡面還有值未取出的 chanel 會發生什麼事? // 結果是先取出 chanel 裡面的值,之後返回零值 func exp3(){ sigCh:= make(chan string) ch:=make (chan int,2) ch <- 3 ch <- 4 close(ch) go func (){ for i:=0;i<6;i++ { fmt.Println("b goroutine recieve: " ,<-ch) } sigCh <-"over" }() <-sigCh }
控制檯結果如圖:
往已經關閉的 chanel 寫入資料
// exp4 用來測試往一個已經關閉的 chanel 寫入資料會發生什麼事? // 結果是發生 panic :send on closed channel func exp4 (){ sigCh:= make(chan string) ch:=make (chan int,2) ch <- 3 ch <- 4 close(ch) go func (){ ch <- 6 sigCh <-"over" }() <-sigCh }
控制檯結果如圖:
關閉已經關閉的 chanel
// exp5 關閉已經關閉的 chanel 會發生什麼事? // 結果是發生 panic : close of closed channel func exp5 (){ sigCh:= make(chan string) ch:=make (chan int,2) ch <- 3 ch <- 4 close(ch) go func (){ close(ch) sigCh <-"over" }() <-sigCh }
chanel 底層實現
chanel 的資料結構
- 基於陣列的迴圈佇列,有緩衝的channel用它暫存資料
- 基於連結串列的單向佇列,用於儲存阻塞在此channel上的goroutine
chanel 的建立
- chanel 是分配在堆上面的
- 使用 make 內建函式返回的就是指標,所以我們函式傳參時不必使用指向 chanel 的指標
chanel 的傳送與接收
傳送資料給 chanel
- acquire 確認鎖狀態
- dequeue 入佇列,將資料拷貝到 buf 佇列上
- release 釋放
從 chanel 接收資料
- acquire 確認鎖狀態
- enqueue 出佇列,拷貝 buf 佇列的資料
- release 釋放
沒有共享記憶體(除了 hchan 物件)“不通過共享記憶體來通訊,而是通過通訊來共享記憶體”,而怎麼通訊呢?其實就是 copies。
補充:執行時呼叫程式
goroutine 是一種使用者級執行緒,由 Go 執行時系統來建立和管理,而不是作業系統。相較於作業系統的執行緒更加輕量級。一個 OS thread 對應對個 goroutine。這便是 Go 語言的 M:N 排程模型。
MGP 模型,其中 P 為排程所需要的上下文資源,擁有執行佇列。
chanel 快取滿,傳送方 goroutine 阻塞
當 G1 傳送資料給已經滿了的 chanel 時,會通過呼叫 gopark() 方法呼叫排程器將當前 goroutine(即 G1)設定為 waiting 狀態。
然後斷開 G1 和 M 的聯絡
排程下一條可執行的 goroutine
chanel 快取有空餘,傳送方 goroutine 喚醒
被阻塞的 傳送方、接收方 goroutine 分別儲存在 hchan 結構體的 sendq 、recvq 欄位中。並且是封裝為一個個 sudog 結構體物件儲存的。
G1 建立一個 sudog 結構體物件,然後將 sudog 放入 hchan 型別物件中的 sendq 佇列上。
接下來,當 G2 取走 chanel 中的資料,chanel 快取有空餘,G1 不再阻塞,但是是誰呼叫排程器將 G1 喚醒的呢?
如上圖,是 G2 ,通過呼叫 goready(G1) 方法呼叫排程器將 G1 喚醒。所以說“通過通訊來共享記憶體”
G1 往一個空的 chanel 接收資料導致阻塞
當 chanel 有資料不為空了,G1 並不是:
- 確認鎖然後
- 取出 buf 陣列中的元素
- goready(G2)
而是有一種更聰明的辦法,直接寫到等待接受佇列中的 G2
總結
- 使用鎖保證併發安全
- 採用 FIFO 先進先出策略儲存
- 以 sudog 結構體型別儲存阻塞的 goroutine 作為佇列節點
- 通過 gopark,goready 來呼叫(calls into)執行時排程器
關於無快取 chanel ,如果接收方先阻塞,則傳送方“直接寫入”資料到接受方的棧結構中。
如果傳送方先阻塞,則接收方直接從傳送方的 sudog 結構中獲取資料。