Go 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
}

控制檯結果如圖:segmentfault

clipboard.png

往已經關閉的 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
}

控制檯結果如圖:數組

clipboard.png

關閉已經關閉的 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
}

clipboard.png

chanel 底層實現

chanel 的數據結構

clipboard.png

  • 基於數組的循環隊列,有緩衝的channel用它暫存數據
  • 基於鏈表的單向隊列,用於保存阻塞在此channel上的goroutine

chanel 的建立

  • chanel 是分配在堆上面的
  • 使用 make 內建函數返回的就是指針,因此咱們函數傳參時沒必要使用指向 chanel 的指針

clipboard.png

chanel 的發送與接收

發送數據給 chanel

  1. acquire 得到鎖
  2. dequeue 入隊列,將數據拷貝到 buf 隊列上
  3. release 釋放鎖

從 chanel 接收數據

  1. acquire 得到鎖
  2. enqueue 出隊列,拷貝 buf 隊列的數據
  3. release 釋放鎖

沒有共享內存(除了 hchan 對象)「不經過共享內存來通訊,而是經過通訊來共享內存」,而怎麼通訊呢?其實就是 copies。
clipboard.png緩存

補充:運行時調用程序

goroutine 是一種用戶級線程,由 Go 運行時系統來建立和管理,而不是操做系統。相較於操做系統的線程更加輕量級。一個 OS thread 對應對個 goroutine。這即是 Go 語言的 M:N 調度模型。
clipboard.png安全

MGP 模型,其中 P 爲調度所須要的上下文資源,擁有運行隊列。
clipboard.png數據結構

chanel 緩存滿,發送方 goroutine 阻塞

當 G1 發送數據給已經滿了的 chanel 時,會經過調用 gopark() 方法呼叫調度器將當前 goroutine(即 G1)設置爲 waiting 狀態。併發

clipboard.png
而後斷開 G1 和 M 的聯繫函數

clipboard.png

調度下一條可運行的 goroutine
clipboard.png測試

chanel 緩存有空餘,發送方 goroutine 喚醒

被阻塞的 發送方、接收方 goroutine 分別保存在 hchan 結構體的 sendq 、recvq 字段中。而且是封裝爲一個個 sudog 結構體對象保存的。
clipboard.pngui

G1 建立一個 sudog 結構體對象,而後將 sudog 放入 hchan 類型對象中的 sendq 隊列上。
clipboard.pngspa

接下來,當 G2 取走 chanel 中的數據,chanel 緩存有空餘,G1 再也不阻塞,可是是誰調用調度器將 G1 喚醒的呢?

clipboard.png
如上圖,是 G2 ,經過調用 goready(G1) 方法呼叫調度器將 G1 喚醒。因此說「經過通訊來共享內存」

clipboard.png

G1 往一個空的 chanel 接收數據致使阻塞

當 chanel 有數據不爲空了,G1 並非:

  1. 確認鎖而後
  2. 取出 buf 數組中的元素
  3. goready(G2)

clipboard.png

而是有一種更聰明的辦法,直接寫到等待接受隊列中的 G2
clipboard.png

clipboard.png

clipboard.png

總結

  • 使用鎖保證併發安全
  • 採用 FIFO 先進先出策略存儲
  • 以 sudog 結構體類型存儲阻塞的 goroutine 做爲隊列節點
  • 經過 gopark,goready 來呼叫(calls into)運行時調度器

clipboard.png

關於無緩存 chanel ,若是接收方先阻塞,則發送方「直接寫入」數據到接受方的棧結構中。
若是發送方先阻塞,則接收方直接從發送方的 sudog 結構中獲取數據。
clipboard.png

引用文章出處:
https://speakerdeck.com/kavya...
https://segmentfault.com/a/11...
相關文章
相關標籤/搜索