「Golang成長之路」併發之Channel下

ice_moss發表於2021-10-04
一、channel(通道)的介紹

如果說goroutine是GO併發的執行體,channel(通道)就是他們的連線。channel是可以讓一個goroutine傳送特定值到另一個goroutine的通訊機制。
「Golang成長之路」併發之channel篇
每一個通道是一個具體的型別,叫做通道的元素型別。如有一個int型別的通道就寫成:
chan int
當然這裡也可以使用內建函式——make函式來建立一個通道:

ch := make(chan int)  //ch是 'chan int ' 型別

二、 channel的語法

  1. chanDemo()
    建立channel:
    和普通的int、float等型別是一樣
    var ch1 chan int         //此時 ch1 == nil
    var ch2 chan float32     //此時 ch2 == nil
    var ch3 chan string      //此時 ch3 == nil
    我們也可以使用內建函式make來建立:
    ch1 := make(chan int)
    ch2 := make(chan float32)
    ch3 := make(chan string)
    package main
    import "fmt"
    func main(){
    ch := make(chan int)  //建立channel
    ch <- 1   //向channel裡發資料
    n := <- ch //從channel收資料
    fmt.Println(n) 
    }
    這裡會報錯:all goroutine air asleep - deadlock!
    原因:channel是用於goroutine和goroutine之間的通訊管道,在上面的程式碼中我們只有一個主goroutine(main)所以沒有人來接收ch中的資訊,會造成死鎖。

這裡我們需要啟動一個goroutine:

package main

import "fmt"

func chanDemo() {
   ch := make(chan int)
   go func(){  //參見函數語言程式設計:https://learnku.com/articles/59902
      for {
      n := <-ch
         fmt.Println(n)
      }
   }()
   ch <- 100
   ch <- 200
   ch <- 300
  time.Sleep(time.Millisecond) //為了讓所有資料輸出,需要規定程式執行時間
}
func mian(){
    chanDemo()
}

列印結果為:

100
200
300
  1. channel可作為引數
    在函數語言程式設計中函式是一等公民,函式可作為引數、返回值等
    channel也一樣,也可以作為引數,返回值。
package main

import "fmt"

func worker(id int ch chan int ){ //將channel作為引數
    for {
       n := <- ch
       fmt.Printf("worker %d received %d\n",id, n )
    }
}
func chanDemo(){
    ch := make( chan int)
    go worker()  //開一個併發
    ch <- 100
    ch <- 200
    ch <- 300
    time.Sleep(time.Millisecond) //為了讓所有資料輸出,需要規定程式執行時間
}

func main(){
    chanDemo()
}

列印結果為:

worker 0 received 100
worker 0 received 200
worker 0 received 300

這裡我們可以隨意建立,建立10goroutine:
將chanDemo()改一下

func chanDemo(){
   var channels [10]chan int //建立channel陣列
   for i := 0; i < 10; i++{
      channels[i] = make(chan int)
      go worker(i, channels[i]) //建立10個goroutine
   }
   for i := 0; i < 10; i++{
      channels[i] <- 'a' + 1
   }
   time.Sleep(time.Millisecond)
}

列印結果:
worker 4 received 98
worker 0 received 98
worker 1 received 98
worker 2 received 98
worker 9 received 98
worker 6 received 98
worker 3 received 98
worker 8 received 98
worker 5 received 98
worker 7 received 98
* 分析:在修改的chanDemo()中,我們開了10個goroutine,而每一個goroutine都分發了一個channel,從達到每一個goroutine都可以和主goroutine(main)通訊。*

  1. channel可作返回值
    同樣channel也可以作為返回值
    func creatworker(id int) chan int { 
       c := make(chan int)
       go func() {
         for {
            n := <-c
            fmt.Printf("worker %d received %d\n", id, n)
         }
      }()
      return c
      //在creatworker()中主要是go func()在真正的在做事,c建立後立即被返回
    }
    //
    func chanDemo(){
      var channels [10]chan int //建立channel陣列
      for i := 0; i < 10; i++{
         channels[i] = creatworker(i)
      }
      for i := 0; i < 10; i++{
         channels[i] <- 'a' + 1
      }
      time.Sleep(time.Millisecond)
    }
    //
    func main() {
    chanDemo()
    }
    列印結果:
    worker 9 received 98
    worker 8 received 98
    worker 5 received 98
    worker 6 received 98
    worker 0 received 98
    worker 3 received 98
    worker 2 received 98
    worker 1 received 98
    worker 4 received 98
    worker 7 received 98

3.bufferedChannel(緩衝通道)
在前面內容裡:

package main
import "fmt"
func main(){
ch := make(chan int)  //建立channel
ch <- 1   //向channel裡發資料
   n := <- ch //從channel收資料
fmt.Println(n) 
}

會報錯:all goroutine air asleep - deadlock!
是因為沒有人去收channel的資料,但是在上面程式碼中我們發了1,就必須收1,這樣比較好資源,所以我們使用緩衝通道,就可有避免死鎖了。
我們這樣定義:

ch := make(chan int, 3)  //建立一個緩衝容量為3的通道
func bufferedChannel(){
   ch := make(chan int, 3)
   ch <- 1
   ch <- 2
   ch <- 3
}

func main() {
  bufferedChannel()
}

這樣run就不會deadlock,當然如果再向ch發資料就會deadlock。
現在仍然使用goroutine來收資料:

func worker(id int,c chan int){
   for {
      n := <-c
      fmt.Printf("worker %d received %c\n", id, n)
   }
}

func bufferedChannel(){
   ch := make(chan int, 3)
   go worker(0, ch)
   for i := 0; i < 10; i++{
   ch <- 'a' + i
   }
  time.Sleep(time.Millisecond)
}

可以看出,只要有人收,緩衝區滿了,也不會deadlock。
列印結果:
worker 0 received a
worker 0 received b
worker 0 received c
worker 0 received d
worker 0 received e
worker 0 received f
worker 0 received g
worker 0 received h
worker 0 received i

  1. channelClose
    channel什麼時候發完了?
    在前面的程式碼中,我們知道channel發完了,原因是:我們在main中呼叫的函式執行結束了,main結束了,程式也就退出了,在併發程式設計中,我們需要知道資料是什麼時候傳送結束的。

(1). close()方法

func worker(id int,c chan int){
   for {
      n := <-c
      fmt.Printf("worker %d received %c\n", id, n)
   }
}
func channelClose(){
   ch := make(chan int, 3)
   go worker(0, ch)
   ch <- 'a'
   ch <- 'b'
   ch <- 'c'
   ch <- 'd'
   close(ch)
   time.Sleep(time.Millisecond)
}
func main(){
channelClose()
}

使用Close方法後資料收完後,就一直列印(列印time.Millisecond的時間)空串(或0)
列印結果:
worker 0 received a
worker 0 received b
worker 0 received c
worker 0 received d
worker 0 received
worker 0 received
worker 0 received
worker 0 received
worker 0 received
worker 0 received
worker 0 received
worker 0 received
worker 0 received
……
……
……

(2). n, ok := <- c

func worker(id int,c chan int){
   for {
      n, ok := <- c
      if !ok{
         break
  }
      fmt.Printf("worker %d received %c\n", id, n)
   }
}
func channelClose(){
   ch := make(chan int)
   go worker(0, ch)
   ch <- 'a'
   ch <- 'b'
   ch <- 'c'
   ch <- 'd'
   close(ch)
   time.Sleep(time.Millisecond)
}
func main() {
 channelClose()
}

列印結果:
worker 0 received a
worker 0 received b
worker 0 received c
worker 0 received d

(3). range

func worker(id int,c chan int){
   for n := range c{
      fmt.Printf("worker %d received %c\n", id, n)
   }
}
func channelClose(){
   ch := make(chan int)
   go worker(0, ch)
   ch <- 'a'
   ch <- 'b'
   ch <- 'c'
   ch <- 'd'
   close(ch)
   time.Sleep(time.Millisecond)
}
func main() {
 channelClose()
}

列印結果:
worker 0 received a
worker 0 received b
worker 0 received c
worker 0 received d

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章