Channel 與Goroutine

channel 與 goroutine 是互相搭配的, main function 本身就是一個 goroutine,另外可以使用 go func 創造 goroutine,可以想像 goroutine 是一個異步的 function,然後搭配 channel 使用,來傳遞資料。

當 channel 有資料傳入或需要從某個 channel 變數讀出資料時,那個 channel 作用域上面的 func (該 goroutine func) 會被 block ,然後把執行環境交給另一個 goroutine,直到 channel 有進有出後才會回到該 goroutine 作用環境。

這邊有一個重點是 buffered channel (初始化時有給第二個參數),寫入但沒讀出且沒超過 channel size時也不會阻塞,但讀出時如果 channel 是空的就會阻塞。

Channel 用法

package main

import (
	"fmt"
)

func main() {
	var test = make(chan int)
	go func() { test <- 123 }() // 如果傳遞值到 channel 時不在 go func 內程式會卡住
	msg := <-test               // channel 是一個地址,要賦予給一個變數後才能讀出
	fmt.Println(test)
	fmt.Println(msg)
}

// 0xc000062060
// 123

因為 Goroutine 是異步 async 的,所以要利用 channel 來傳資料

package main

import "fmt"

func main() {

    messages := make(chan string)
    go func() { messages <- "ping" }()
    msg := <-messages
    fmt.Println(msg)  // ping
}

goroutine 部分也可加上參數寫為

package main

import "fmt"

func main() {

    messages := make(chan string)
    go func(c chan string) { c <- "ping" }(messages) 
    // 功能僅為把原本 messages 參數名改為 c
    msg := <-messages
    fmt.Println(msg)  // ping
}

Goroutine

go func() {....}()

以上及為Goroutine一般的樣子,go code 執行時通常為同步的,所以加上 go 等於是 async,讓他變為非同步執行。

如果有多個Goroutine 做相同的事,放在越後面的會先執行完畢

Goroutine 匿名 function

類似:go func() {} ()

func main() {
  done := make(chan bool, 1)
  go func(c chan bool) {
    time.Sleep(50 * time.Millisecond)
    c <- true
  }(done)
  <-done
}

有關於是否要傳參數進去匿名函式的部分可參考:

差別在於是否在 go func 內可存取到正確的外部變數

https://stackoverflow.com/a/30183893/4622645

Channel

使用 make 創建 channel

messages := make(chan string)

然後用 <-傳訊息到channel

make 第二個參數可以放channel大小

使用 var test chan int 這樣宣告的 channel 因為初始為 nil 無法使用

等待 Goroutine 執行完才結束程式的三種方法

  1. time.Sleep

  2. sync.WaitGroup

  3. 使用 channel

以下利用 c channel 確保 go test1 執行後才會結束程式

func test1(c chan int) {
	fmt.Println("test")
	c <- 1
}

func main() {
	c := make(chan int)
	go test1(c)
	<-c
}

Channel select

可以用select 來做channel 的流程控制,select只有 channel可以用,類似於switch

package main

import "time"
import "fmt"

func main() {

    c1 := make(chan string)
    c2 := make(chan string)

    go func() {
        time.Sleep(time.Second * 1)
        c1 <- "one"
    }()
    go func() {
        time.Sleep(time.Second * 2)
        c2 <- "two"
    }()

    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-c1:
            fmt.Println("received", msg1)
        case msg2 := <-c2:
            fmt.Println("received", msg2)
        }
    }
}

另外

    for i := 0; i < 3; i++ {
        select {
        case a := <-aChan:
            fmt.Println(a)
        case b := <-bChan:
            fmt.Println(b)
        case c := <-cChan:
            fmt.Println(c)

        }
    }

a := <-aChan
b := <-bChan
c := <-cChan
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
相同功能

有關 channel deadlock

如果 make channel 後 沒給值 則會產生 deadlock

解法為使用 select 或是 給 make 第二個參數

用 func 接收多個 channel

package main

import (
	"fmt"
)

func cc(num int, ch ...chan int) {
	for i := 0; i < len(ch); i++ {
		ch[i] <- num
	}
}

func main() {
	var test1 = make(chan int)
	var test2 = make(chan int)
	go cc(100, test1, test2)
	msg1 := <-test1
	msg2 := <-test2
	fmt.Println(msg1, msg2)
}

持續接受 channel

當我們這樣寫,程式接收一次channel 就會結束

package main

import (
	"fmt"
	"time"
)

func main() {
	tick := time.Tick(100 * time.Millisecond)
    msg := <-tick
	fmt.Println(msg)
}

可以用 for 搭配 select 持續監聽

    for {
        select {
        case msg1 := <-c1:
            fmt.Println("received", msg1)
        case msg2 := <-c2:
            fmt.Println("received", msg2)
        }
    }

或是使用 for range

https://gobyexample.com/range-over-channels

package main

import "fmt"

func main() {

    queue := make(chan string, 2)
    queue <- "one"
    queue <- "two"
    close(queue)

    for elem := range queue {
        fmt.Println(elem)
    }
}

推薦閱讀:

https://peterhpchen.github.io/2020/03/08/goroutine-and-channel.html

Last updated