Go語言使用select切換協程

2020-07-16 10:05:23
從不同的並行執行的協程中獲取值可以通過關鍵字 select 來完成,它和 switch 控制語句非常相似也被稱作通訊開關;它的行為像是“你準備好了嗎”的輪詢機制;select 監聽進入通道的資料,也可以是用通道傳送值的時候。

select {
case u:= <- ch1:
    ...
case v:= <- ch2:
    ...
    ...
default: // no value ready to be received
    ...
}

default 語句是可選的;fallthrough 行為,和普通的 switch 相似,是不允許的。在任何一個 case 中執行 break 或者 return,select 就結束了。

select 做的就是:選擇處理列出的多個通訊情況中的一個。
  • 如果都阻塞了,會等待直到其中一個可以處理
  • 如果多個可以處理,隨機選擇一個
  • 如果沒有通道操作可以處理並且寫了 default 語句,它就會執行:default 永遠是可執行的(這就是準備好了,可以執行)。

在 select 中使用傳送操作並且有 default 可以確保傳送不被阻塞!如果沒有 case,select 就會一直阻塞。

select 語句實現了一種監聽模式,通常用在(無限)迴圈中;在某種情況下,通過 break 語句使迴圈退出。

在下面的範例程式中有 2 個通道 ch1 和 ch2 ,三個協程 pump1() 、pump2() 和 suck() 。這是一個典型的生產者消費者模式。在無限迴圈中,ch1 和 ch2 通過 pump1() 和 pump2() 填充整數;suck() 也是在無限迴圈中輪詢輸入的,通過 select 語句獲取 ch1 和 ch2 的整數並輸出。選擇哪一個 case 取決於哪一個通道收到了資訊。程式在 main 執行 1 秒後結束。
package main
import (
    "fmt"
    "time"
)
func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    go pump1(ch1)
    go pump2(ch2)
    go suck(ch1, ch2)
    time.Sleep(1e9)
}
func pump1(ch chan int) {
    for i := 0; ; i++ {
        ch <- i * 2
    }
}
func pump2(ch chan int) {
    for i := 0; ; i++ {
        ch <- i + 5
    }
}
func suck(ch1, ch2 chan int) {
    for {
        select {
        case v := <-ch1:
            fmt.Printf("Received on channel 1: %dn", v)
        case v := <-ch2:
            fmt.Printf("Received on channel 2: %dn", v)
        }
    }
}
輸出:

Received on channel 2: 5
Received on channel 2: 6
Received on channel 1: 0
Received on channel 2: 7
Received on channel 2: 8
Received on channel 2: 9
Received on channel 2: 10
Received on channel 1: 2
Received on channel 2: 11
...
Received on channel 2: 47404
Received on channel 1: 94346
Received on channel 1: 94348

一秒內的輸出非常驚人,如果我們給它計數,得到了 90000 個左右的數位。