概述
select
類似 switch
, 包含一系列邏輯分支和一個可選的預設分支。每一個分支對應通道上的一次操作 (傳送或接收),
可以將 select
理解為專門針對通道操作的 switch
語句。
語法規則
select {
case v1 := <- ch1:
// do something ...
case v2 := <- ch2:
// do something ...
default:
// do something ...
}
執行順序
- 當同時存在多個滿足條件的通道時,隨機選擇一個執行
如果沒有滿足條件的通道時,檢測是否存在 default 分支
- 如果存在則執行
- 否則阻塞等待
通常情況下,把含有 default 分支
的 select
操作稱為 無阻塞通道操作
。
例子
隨機執行一個
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
done := make(chan bool)
go func() {
ch1 <- "hello"
}()
go func() {
ch2 <- "world"
}()
go func() {
done <- true
}()
time.Sleep(time.Second) // 休眠 1 秒
// 此時 3 個通道應該都滿足條件,select 會隨機選擇一個執行
select {
case msg := <-ch1:
fmt.Printf("ch1 msg = %s\n", msg)
case msg := <-ch2:
fmt.Printf("ch2 msg = %s\n", msg)
case <-done:
fmt.Println("done !")
}
close(ch1)
close(ch2)
close(done)
}
// $ go run main.go
// 輸出如下,你的輸出可能和這裡的不一樣, 多執行幾次看看效果
/**
ch1 msg = hello
*/
default (無阻塞通道操作)
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
done := make(chan bool)
go func() {
time.Sleep(time.Second)
ch1 <- "hello"
}()
go func() {
time.Sleep(time.Second)
ch2 <- "world"
}()
go func() {
time.Sleep(time.Second)
done <- true
}()
// 此時 3 個通道都在休眠中, 不滿足條件,select 會執行 default 分支
select {
case msg := <-ch1:
fmt.Printf("ch1 msg = %s\n", msg)
case msg := <-ch2:
fmt.Printf("ch2 msg = %s\n", msg)
case <-done:
fmt.Println("done !")
default:
fmt.Println("default !")
}
close(ch1)
close(ch2)
close(done)
}
// $ go run main.go
// 輸出如下
/**
default !
*/
和 for 搭配使用
透過在 select
外層加一個 for
迴圈,可以達到 無限輪詢
的效果。
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
done := make(chan bool)
go func() {
// ch1 goroutine 輸出 1 次
fmt.Println("[ch1 goroutine]")
time.Sleep(time.Second)
ch1 <- "hello"
}()
go func() {
// ch2 goroutine 輸出 2 次
for i := 0; i < 2; i++ {
fmt.Println("[ch2 goroutine]")
time.Sleep(time.Second)
}
ch2 <- "world"
}()
go func() {
// done goroutine 輸出 3 次
for i := 0; i < 3; i++ {
fmt.Println("[done goroutine]")
time.Sleep(time.Second)
}
done <- true
}()
for exit := true; exit; {
select {
case msg := <-ch1:
fmt.Printf("ch1 msg = %s\n", msg)
case msg := <-ch2:
fmt.Printf("ch2 msg = %s\n", msg)
case <-done:
fmt.Println("done !")
exit = false // 透過變數控制外層 for 迴圈退出
}
}
close(ch1)
close(ch2)
close(done)
}
// $ go run main.go
// 輸出如下,你的輸出順序可能和這裡的不一樣
/**
[done goroutine]
[ch2 goroutine]
[ch1 goroutine]
ch1 msg = hello
[done goroutine]
[ch2 goroutine]
ch2 msg = world
[done goroutine]
done !
*/
從輸出結果看,[ch1 goroutine]
輸出了 1 次,[ch2 goroutine]
輸出了 2 次,[done goroutine]
輸出了 3 次。