5.2.5 select
Go语言作为天生的多线程语言,经常要在程序中同时使用或处理多个channel,并且经常需要根据不同的channel读写进行不同的操作。所以,Go语言提供了select关键字。可以把select理解为switch case,只是select是根据不同的channel通信进入不同的处理代码。
select有监听channel通信的作用,当有通信发生时就触发相应的代码块。
select的基本用法和大致结构如下:
select{ case <-ch1: //如果从ch1读取数据,则执行该部分代码块 case ch2 <- 1 : //如果向ch2写入数据,则执行该部分代码块 default: //如果前面的case都不满足,则执行此部分代码块 }
下面通过一个简单的例子来看一下select的使用:
book/ch05/5.2/select/main.go
1. package main
2.
3. import (
4. "fmt"
5. )
6.
7. func main() {
8. ch1 := make(chan int)
9. ch2 := make(chan int)
10. go func() {
11. //time.Sleep(3*time.Second)
12. ch1 <- 1
13. }()
14. go func() {
15. //time.Sleep(5*time.Second)
16. ch2 <- 2
17. }()
18.
19. select {
20. case <-ch1:
21. fmt.Println("can read from ch1")
22. case <-ch2:
23. fmt.Println("can read from ch2")
24. //default:
25. // fmt.Println("default...")
26. }
27. }
这是一个非常简单的使用select的例子。
第8行和第9行定义了两个int型通道。
第10行至第17行,启动了两个goroutine,运行了两个匿名函数,函数的作用分别是为ch1和ch2写数据。
第19行至第27行,通过select判断是ch1还是ch2可读,进而打印不同的信息。要注意的是,select内的case不是顺序执行的,并非每次都是先判断写在前面的case,而是会并行判断,当两个条件都满足的时候会相对公平地选择一个执行。读者可以执行上述代码,看一下是不是打印ch1相关信息和打印ch2相关信息的概率差不多。
注意,对于注释掉的第24行和第25行,如果去掉注释,很可能就会执行default,因为主goroutine和前面启动的两个goroutine是并发执行,在判断case的时候数据往往还没有写入ch1和ch2,这时候就会执行default。而当去掉default的时候,因为没有满足条件的case,所以select会进入阻塞状态。读者可以去掉第11行和第15行的注释再执行代码,看一下结果是否又会不同。
注意
select可以管理和编排多个channel,是并发编程当中非常重要的应用。但是一定要注意使用selelct时的死锁问题,在开发过程中要格外小心。