1. 程式人生 > 其它 >【GO】併發程式設計基礎-channel

【GO】併發程式設計基礎-channel

協程(Goroutine)

Go 語言中沒有執行緒的概念,只有協程,也稱為 goroutine。相比執行緒來說,協程更加輕量,一個程式可以隨意啟動成千上萬個 goroutine。

goroutine 被 Go runtime 所排程,這一點和執行緒不一樣。也就是說,Go 語言的併發是由 Go 自己所排程的,自己決定同時執行多少個 goroutine,什麼時候執行哪幾個。這些對於我們開發者來說完全透明,只需要在編碼的時候告訴 Go 語言要啟動幾個 goroutine,至於如何排程執行,我們不用關心。

要啟動一個 goroutine 非常簡單,Go 語言為我們提供了 go 關鍵字,相比其他程式語言簡化了很多,如下面的程式碼所示:

func main() {
 go func() {
       fmt.Print("我是新建立的協程")
   }()
}

go 關鍵字後跟一個方法或者函式的呼叫,就可以啟動一個 goroutine

協程之間通訊(channel)

Go 語言提供的 channel(通道)來解決這個問題

宣告一個 channel

ch:=make(chan string)

其中 chan 是一個關鍵字,表示是 channel 型別。
後面的 string 表示 channel 裡的資料是 string 型別。

通過 channel 的宣告也可以看到,chan 是一個集合型別。

定義好 chan 後就可以使用了,一個 chan 的操作只有兩種:傳送和接收

  1. 接收:獲取 chan 中的值,操作符為 <- chan。
  2. 傳送:向 chan 傳送值,把值放在 chan 中,操作符為 chan <-。

注意:這裡注意傳送和接收的操作符,都是 <- ,只不過位置不同。接收的 <- 操作符在 chan 的左側,傳送的 <- 操作符在 chan 的右側。

使用例項1:無緩衝channel(同步channel)

如果傳送方一直沒有傳送資料,則接收方會一直阻塞,使用 make 建立的 chan 就是一個無緩衝 channel,它的容量是 0,不能儲存任何資料。所以無緩衝 channel 只起到傳輸資料的作用,資料並不會在 channel 中做任何停留。這也意味著,無緩衝 channel 的傳送和接收操作是同時進行的,它也可以稱為同步 channel

func TestChannelWithNoBuffer(t *testing.T) {
   ch := make(chan int)
   go func() {
      ch <- 1
   }()
   t.Log("我是main gorountie")
   go func() {
      v := <-ch
      t.Log("獲得資料", v)
   }()
}

使用例項2:有緩衝channel

有緩衝 channel 類似一個可阻塞的佇列,內部的元素先進先出。通過 make 函式的第二個引數可以指定 channel 容量的大小,進而建立一個有緩衝 channel,如下面的程式碼所示:

cacheCh:=make(chan int,5)

建立了一個容量為 5 的 channel,內部的元素型別是 int,也就是說這個 channel 內部最多可以存放 5 個型別為 int 的元素

一個有緩衝 channel 具備以下特點:

  1. 有緩衝 channel 的內部有一個緩衝佇列;
  2. 傳送操作是向佇列的尾部插入元素,如果佇列已滿,則阻塞等待,直到另一個 goroutine 執行,接收操作釋放佇列的空間;
  3. 接收操作是從佇列的頭部獲取元素並把它從佇列中刪除,如果佇列為空,則阻塞等待,直到另一個 goroutine 執行,傳送操作插入新的元素。
func TestChannelWithBuffer(t *testing.T) {
   ch := make(chan string, 10)
   go func() {
      fmt.Println("balabala")
      ch <- "goroutine 完成"
   }()
   fmt.Println("我是main goroutine")
   ch <- "main goroutine 輸入的值"
   v := <-ch
   fmt.Println("等待,接收到channel中的值:", v)
}

關閉 channel

channel 還可以使用內建函式 close 關閉

close(cacheCh)

如果一個 channel 被關閉了,就不能向裡面傳送資料了,如果傳送的話,會引起 painc 異常。但是還可以接收 channel 裡的資料,如果 channel 裡沒有資料的話,接收的資料是元素型別的零值。

多路複用:select+channel

func TestSelectChannel(t *testing.T) {
   fir := make(chan int)
   sec := make(chan int)
   tir := make(chan int)
   go func() {
      fir <- 1
   }()
   go func() {
      sec <- 2
   }()
   go func() {
      tir <- 3
   }()
   select {
     case filePath := <-fir:
        t.Log(filePath)
     case filePath := <-sec:
        t.Log(filePath)
     case filePath := <-tir:
        t.Log(filePath)
   }
}

整體結構和 switch 非常像,都有 case 和 default,只不過 select 的 case 是一個個可以操作的 channel。多路複用可以簡單地理解為,N 個 channel 中,任意一個 channel 有資料產生,select 都可以監聽到,然後執行相應的分支,接收資料並處理
在 Go 語言中,提倡通過通訊來共享記憶體,而不是通過共享記憶體來通訊,其實就是提倡通過 channel 傳送接收訊息的方式進行資料傳遞,而不是通過修改同一個變數。所以在資料流動、傳遞的場景中要優先使用 channel,它是併發安全的,效能也不錯。