首頁 > 軟體

Go語言入門學習之Channel通道詳解

2022-07-24 14:01:52

前言

不同於傳統的多執行緒並行模型使用共用記憶體來實現執行緒間通訊的方式,go 是通過 channel 進行協程 (goroutine) 之間的通訊來實現資料共用。

channel,就是一個管道,可以想像成 Go 協程之間通訊的管道。它是一種佇列式的資料結構,遵循先入先出的規則。

通道的宣告

每個通道都只能傳遞一種資料型別的資料,宣告時需要指定通道的型別。chan Type 表示 Type 型別的通道。通道的零值為 nil 。

var channel_name chan channel_types 
var str chan string 

通道的初始化

宣告完通道後,通道的值為 nil ,不能直接使用,使用 make 函數對通道進行初始化操作。

channel_name = make(chan channel_type) 
str = make(chan string) 

或者

str := make(chan string) 

傳送和接收資料

傳送資料,把 data 資料傳送到 channel_name 通道中。

channel_name <- data 

接收資料,從 channel_name 通道中接收資料到 value。

value := <- channel_name 
func PrintFunc(c chan string) {
   c <- "往通道里面傳資料"
}

func main() {
   str := make(chan string)
   fmt.Println("start")
   go PrintFunc(str)
   result := <-str
   fmt.Println(result)
   fmt.Println("end")
}

傳送與接收預設是阻塞的。如果從通道接收資料沒接收完主協程是不會繼續執行下去的。當把資料傳送到通道時,會在傳送資料的語句處發生阻塞,直到有其它協程從通道讀取到資料,才會解除阻塞。與此類似,當讀取通道的資料時,如果沒有其它的協程把資料寫入到這個通道,那麼讀取過程就會一直阻塞著。

通道的關閉

對於一個已經使用完畢的通道,我們要將其進行關閉。對於一個已經關閉的通道如果再次關閉會導致報錯。

close(channel_name) 

可以在接收資料時,判斷通道是否已經關閉,從通道讀取資料返回的第二個值表示通道是否沒被關閉,如果已經關閉,返回值為 false ;如果還未關閉,返回值為 true 。

value, ok := <- channel_name 

通道的容量與長度

通道可以設定緩衝區,通過 make 的第二個引數指定緩衝區大小

ch := make(chan int, 100)
  • 0:通道中不能存放資料,在傳送資料時,必須要求立馬接收,否則會報錯。此時的通道稱之為無緩衝通道。
  • 1:通道只能快取一個資料,若通道中已有一個資料,此時再往裡傳送資料,會造成程式阻塞。利用這點可以利用通道來做鎖。
  • 大於 1 :通道中可以存放多個資料,可以用於多個協程之間的通訊管道,共用資源。

通過 cap 函數和 len 函數獲取通道的容量和長度。

func main() {
   // 建立一個通道
   c := make(chan int, 5)
   fmt.Println("初始化:")
   fmt.Println("cap:", cap(c))
   fmt.Println("len:", len(c))
   c <- 1
   c <- 2
   c <- 3
   fmt.Println("傳入資料:")
   fmt.Println("cap:", cap(c))
   fmt.Println("len:", len(c))
   <-c
   fmt.Println("取出一個數:")
   fmt.Println("cap:", cap(c))
   fmt.Println("len:", len(c))
}

緩衝通道與無緩衝通道

帶緩衝區的通道允許傳送端的資料傳送和接收端的資料獲取處於非同步狀態,就是說傳送端傳送的資料可以放在緩衝區裡面,可以等待接收端去獲取資料,而不是立刻需要接收端去獲取資料。

不過由於緩衝區的大小是有限的,所以還是必須有接收端來接收資料的,否則緩衝區一滿,資料傳送端就無法再傳送資料了。

通道不帶緩衝,傳送方會阻塞直到接收方從通道中接收了值。如果通道帶緩衝,傳送方則會阻塞直到傳送的值被拷貝到緩衝區內;如果緩衝區已滿,則意味著需要等待直到某個接收方獲取到一個值。接收方在有值可以接收之前會一直阻塞。

c := make(chan int) 
// 或者 
c := make(chan int, 0) 

緩衝通道允許通道里儲存一個或多個資料,設定緩衝區後,傳送端和接收端可以處於非同步的狀態。

c := make(chan int, 3) 

雙向通道和單向通道

雙向通道:既可以傳送資料也可以接收資料

func main() {
   // 建立一個通道
   c := make(chan int)

   // 傳送資料
   go func() {
      fmt.Println("send: 1")
      c <- 1
   }()

   // 接收資料
   go func() {
      n := <-c
      fmt.Println("receive:", n)
   }()

   // 主協程休眠
   time.Sleep(time.Millisecond)
}

單向通道:只能傳送或者接收資料。具體細分為唯讀通道和只寫通道。

<-chan 表示唯讀通道:

// 定義唯讀通道
c := make(chan string)
// 定義型別
type Receiver = <-chan string
var receiver Receiver = c

// 或者簡單寫成下面的形式
type Receiver = <-chan int
receiver := make(Receiver)

chan<- 表示只寫通道:

// 定義只寫通道
c := make(chan int)
// 定義型別
type Sender = chan<- int
var sender Sender = c

// 或者簡單寫成下面的形式
type Sender = chan<- int
sender := make(Sender)
package main

import (
   "fmt"
   "time"
)

// Sender 只寫通道型別
type Sender = chan<- string

// Receiver 唯讀通道型別
type Receiver = <-chan string

func main() {
   // 建立一個雙向通道
   var ch = make(chan string)

   // 開啟一個協程
   go func() {
      // 只寫通道
      var sender Sender = ch
      fmt.Println("write only start:")
      sender <- "Go"
   }()

   // 開啟一個協程
   go func() {
      // 唯讀通道
      var receiver Receiver = ch
      message := <-receiver
      fmt.Println("readonly start: ", message)
   }()

   time.Sleep(time.Millisecond)
}

遍歷通道

使用 for range 迴圈可以遍歷通道,但在遍歷時要確保通道是處於關閉狀態,否則迴圈會被阻塞。

package main

import (
   "fmt"
)

func loopPrint(c chan int) {
   for i := 0; i < 10; i++ {
      c <- i
   }
   // 記得要關閉通道
   // 否則主協程遍歷完不會結束,而會阻塞
   close(c)
}

func main() {
   // 建立一個通道
   var ch2 = make(chan int, 5)
   go loopPrint(ch2)
   for v := range ch2 {
      fmt.Println(v)
   }
}

fibonacci 數列

package main

import (
   "fmt"
)

func fibonacci(n int, c chan int) {
   x, y := 0, 1
   for i := 0; i < n; i++ {
      c <- x
      x, y = y, x+y
   }
   close(c)
}

func main() {
   c := make(chan int, 10)
   go fibonacci(cap(c), c)
   // range 函數遍歷每個從通道接收到的資料,因為 c 在傳送完 10 個
   // 資料之後就關閉了通道,所以這裡我們 range 函數在接收到 10 個資料
   // 之後就結束了。如果上面的 c 通道不關閉,那麼 range 函數就不
   // 會結束,從而在接收第 11 個資料的時候就阻塞了。
   for i := range c {
      fmt.Println(i)
   }
}

參考文章:

go-edu.cn/

www.runoob.com/go/go-tutor…

總結

到此這篇關於Go語言入門學習之Channel通道的文章就介紹到這了,更多相關Go語言Channel通道內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com