Go 通道(chanel)詳解

LiberHome發表於2022-05-24

    不要通過共享記憶體來通訊 應該通過通訊來共享記憶體
    這句話有網友的解釋如下:

這句俏皮話具體說來就是,不同的執行緒不共享記憶體不用鎖,執行緒之間通訊用通道(channel)同步也用channel。

    chanel是協程之間傳遞資訊的媒介,優雅地解決了某些後端開發常用語言中隨處可見的lock,unlock,臨界區等,把從很多執行緒層面解決的問題移到協程,從而靜態地保證沒有資料競爭。


通道的宣告與建立

虛擬碼如下:

//宣告型別
var 通道名 chan 資料型別
//建立通道
通道名 = make(chan 資料型別)

實際例子如下:

package main

import "fmt"

func main() {
    var a chan int
    fmt.Printf("%T, %v\n", a, a)

    if a == nil {
        a = make(chan int)
        fmt.Printf("%T, %v\n", a, a)
    }
}

執行結果是:

chan int, <nil>
chan int, 0x1400001a360

通道是一個記憶體地址,這也說明了其實一個引用型別的資料。


接收 & 傳送資料

    對於同一個通道來講,他的讀資料 和 寫資料 都是阻塞的。
虛擬碼如下:

//從通道讀資料
data := <-a
//把資料寫入通道
a <- data

實際例子如下:

package main

import "fmt"

func main() {
    //    首先建立一個bool型別的通道
    var ch1 chan bool
    ch1 = make(chan bool)
    //下面啟動一個go routine
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println("子goroutine中, i: ", i)
        }
        fmt.Println("completed")
        //迴圈結束後 向團隊中寫資料,表示要結束了
        ch1 <- true
    }()
    //在主程式中讀取資料
    data := <-ch1
    //列印一下 我們讀到的資料
    fmt.Println("main  data: ", data)
    fmt.Println("main goroutine completed")
}

執行結果如下:

子goroutine中, i:  0
子goroutine中, i:  1
子goroutine中, i:  2
子goroutine中, i:  3
子goroutine中, i:  4
子goroutine中, i:  5
子goroutine中, i:  6
子goroutine中, i:  7
子goroutine中, i:  8
子goroutine中, i:  9
completed
main  data:  true
main goroutine completed

    我們的子goroutine裡面 迴圈列印1~10, 列印完成之後 把chanel型別的ch1寫為true,
這時候,主goroutine就可以根據這一條件進行下一步了,,在此之前,其實就算主goroutine先搶到了資源,從ch1中讀取資料,但是現在通道里面啥都沒有,只能阻塞,然後乖乖交出資源給我們的子goroutine,直到迴圈結束寫true入ch1。
    需要注意的有以下幾點:

  • chanel是需要指定型別的 nil型別的chanel不能直接使用。
  • chanel本身是同步的,同一時間只能有一條goroutine進行操作。
  • chanel是goroutine之間傳遞資料用的,chanel資料的傳送和接收必須在不同的goroutine中,如果只有一條goroutine是用不上chanel的,這種情況會發生死鎖(deadLock)。
  • 從chanel裡面讀資料立馬就會被阻塞,直到有向chanel寫資料的goroutine來。
  • 向chanel裡面寫資料立馬就會被阻塞,直到有從chanel讀資料的goroutine來。

(以上都是相對於沒有快取的通道而言,後面講到的快取通道在緩衝區滿的時候才阻塞,而不是立刻阻塞)


參考:bilibili

相關文章