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

前言

不同於傳統的多線程並發模型使用共享內存來實現線程間通信的方式,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通道內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: