select 跟 switch 有个共同特性就是都通过 case 的方式来处理,但是 select 跟 switch 处理的事情完全不同,也完全不相容。
switch
特性: 
- 1.各种类型及型别操作,接口 interface{} 型别判断 variable.(type)
- 2.会依照 case 顺序依序执行
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 
 | package main
 var (
 i interface{}
 )
 
 func convert(i interface{}) {
 switch t := i.(type) {
 caseint:
 println("i is interger", t)
 casestring:
 println("i is string", t)
 casefloat64:
 println("i is float64", t)
 default:
 println("type not found")
 }
 }
 
 func main() {
 i = 100
 convert(i)
 i = float64(45.55)
 convert(i)
 i = "foo"
 convert(i)
 convert(float32(10.0))
 }
 
 | 
运行出来的结果如下:
| 12
 3
 4
 
 | i is interger 100i is float64 +4.555000e+001
 i is string foo
 type not found
 
 | 
而 select 的特性就不同了,只能接收 channel,否则会出错,而 default 会直接执行,所以没有 default 的 select 就会遇到 blocking,假设没有送 value 进去 Channel 就会造成 panic,底下拿几个实际例子来解说。
select
Random Select
同一个 channel 在 select 会随机选取,底下看个例子:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | package main
 import"fmt"
 
 func main() {
 ch := make(chan int, 1)
 
 ch <- 1
 select {
 case <-ch:
 fmt.Println("random 01")
 case <-ch:
 fmt.Println("random 02")
 }
 }
 
 | 
执行后会发现有时候拿到 random 01 有时候拿到 random 02,这就是 select 的特性之一,case 是随机选取,所以当 select 有两个 channel 以上时,如果同时对全部 channel 送资料,则会随机选取到不同的 Channel。而上面有提到另一个特性『假设没有送 value 进入 Channel 就会造成 panic』,拿上面例子来改:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | func main() {ch := make(chan int, 1)
 
 select {
 case <-ch:
 fmt.Println("random 01")
 case <-ch:
 fmt.Println("random 02")
 }
 }
 
 | 
执行后会发现变成 deadlock,造成 main 主程式爆炸,这时候可以直接用 default 方式解决此问题:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | func main() {ch := make(chan int, 1)
 
 select {
 case <-ch:
 fmt.Println("random 01")
 case <-ch:
 fmt.Println("random 02")
 default:
 fmt.Println("exit")
 }
 }
 
 | 
主程式 main 就不会因为读不到 channel value 造成整个程式 deadlock。
Timeout 超时机制
用 select 读取 channle 时,一定会实作超过一定时间后就做其他事情,而不是一直 blocking 在 select 内。底下是简单的例子:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | package main
 import (
 "fmt"
 "time"
 )
 
 func main() {
 timeout := make(chan bool, 1)
 go func() {
 time.Sleep(2 * time.Second)
 timeout <- true
 }()
 ch := make(chan int)
 select {
 case <-ch:
 case <-timeout:
 fmt.Println("timeout 01")
 }
 }
 
 | 
建立 timeout channel,让其他地方可以透过 trigger timeout channel 达到让 select 执行结束,也或者有另一个写法是透握 time.After 机制
| 12
 3
 4
 5
 6
 7
 
 | select {case <-ch:
 case <-timeout:
 fmt.Println("timeout 01")
 case <-time.After(time.Second * 1):
 fmt.Println("timeout 02")
 }
 
 | 
可以注意 time.After 是回传 chan time.Time,所以执行 select 超过一秒时,就会输出 timeout 02。
检查 channel 是否已满
直接来看例子比较快:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | package main
 import (
 "fmt"
 )
 
 func main() {
 ch := make(chan int, 1)
 ch <- 1
 select {
 case ch <- 2:
 fmt.Println("channel value is", <-ch)
 fmt.Println("channel value is", <-ch)
 default:
 fmt.Println("channel blocking")
 }
 }
 
 | 
先宣告 buffer size 为 1 的 channel,先丢值把 channel 填满。这时候可以透过 select + default 方式来确保 channel 是否已满,上面例子会输出 channel blocking,我们再把程式改成底下
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | func main() {ch := make(chanint, 2)
 ch <- 1
 select {
 case ch <- 2:
 fmt.Println("channel value is", <-ch)
 fmt.Println("channel value is", <-ch)
 default:
 fmt.Println("channel blocking")
 }
 }
 
 | 
把 buffer size 改为 2 后,就可以继续在塞 value 进去 channel 了,这边的 buffer channel 观念可以看之前的文章『用五分钟了解什么是 unbuffered vs buffered channel』[1]
select for loop 用法
如果你有多个 channel 需要读取,而读取是不间断的,就必须使用 for + select 机制来实现,更详细的实作可以参考『15 分钟学习 Go 语言如何处理多个 Channel 通道』
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 
 | package main
 import (
 "fmt"
 "time"
 )
 
 func main() {
 i := 0
 ch := make(chan string, 0)
 defer func() {
 close(ch)
 }()
 
 go func() {
 LOOP:
 for {
 time.Sleep(1 * time.Second)
 fmt.Println(time.Now().Unix())
 i++
 
 select {
 case m := <-ch:
 println(m)
 break LOOP
 default:
 }
 }
 }()
 
 time.Sleep(time.Second * 4)
 ch <- "stop"
 }
 
 | 
上面例子可以发现执行后如下:
| 12
 3
 4
 5
 6
 
 | 12578940011257894002
 1257894003
 1257894004
 1257894005
 stop
 
 | 
其实把 default 拿掉也可以达到目的
| 12
 3
 4
 
 | select {case m := <-ch:
 println(m)
 break LOOP
 
 | 
当没有值送进来时,就会一直停在 select 区段,所以其实没有 default 也是可以正常运作的,而要结束 for 或 select 都需要透过 break 来结束,但是要在 select 区间直接结束掉 for 回圈,只能使用 break variable 来结束,这边是大家需要注意的地方。
参考资料
[1]『用五分钟了解什么是 unbuffered vs buffered channel』: https://blog.wu-boy.com/2019/04/understand-unbuffered-vs-buffered-channel-in-five-minutes/
Thinks
原文链接:https://blog.wu-boy.com/2019/11/four-tips-with-select-in-golang/