简介
在go
中有一个类似switch
的关键字,那就是select
。
select
的每个case
接收的是I/O
通讯操作,不能有其他表达式。select
要配合channel
使用。
select
的语法结构是这样的:
select { case 表达式: 执行语句 case 表达式 : 执行语句 default : 执行语句 }
上面的表达是可以是chan
的写入(chan<-
)或者是读取(<-chan
)。
其中case
的数量没有限制,每个case
会随机执行。default
是可选的,可以不用这个字段。但是当没有default
时,select
在没有接收到channel
通道数据时就会被阻塞,所以为了程序的健壮性,建议还是带上default
字段(default
代码快可以为空)。
随机读取
我们可以用select
来随机使用case
来读取一个channel
。代码可以这样写:
package main import ( "fmt" "time" ) func main() { // 定义一个100容量的channel ch:=make(chan string,100) go func() { for { // 向channel写入数据 ch <- "hello go!" time.Sleep(1*time.Second) } }() for { select { // 读取数据 case i:= <- ch: fmt.Printf("第1个case:%s\n",i) case j:= <- ch: fmt.Printf("第2个case:%s\n",j) default: } } }
上面这段代码中,我没每过一秒向ch
里写入hello go!
这个字符串数据。然后select
语句中,两个case
会随机进被执行,同时channel
里的数据会被读取然后复制到对应变量上。当channel
里的没有数据被读取时,default
就会会被执行(虽然它里面没有执行代码块)。上面程序输出:
第1个case:hello go! 第2个case:hello go! 第1个case:hello go! 第2个case:hello go! 第2个case:hello go! ......
随机写入
select
除了可以读取channel
里的数据外,还可以向channel
里写入数据。代码可以这样写:
package main import ( "fmt" "time" ) func main() { ch:=make(chan string,100) go func() { for { // 读取数据 s:=<-ch fmt.Printf("接收数据:%s\n",s) } }() for { // 随机写入 select { case ch <- "第1个case写入": case ch <- "第2个case写入": } time.Sleep(1*time.Second) } }
上面代码中,我们每过1秒中随机向ch
通道里写入数据。由于select
对于写操作总是会被执行,所以就不用default
了。上面的代码会输出:
接收数据:第1个case写入 接收数据:第2个case写入 接收数据:第1个case写入 接收数据:第2个case写入 接收数据:第2个case写入 ......
混合读写
当然我们可以在select
里同时向通道读数据和写数据,不过要注意的是,当select
里有多个case
可以被执行时,select
就会随机执行一个。也就是说每次只有一个case
会被执行。为了便于理解,我们代码可以这样写:
package main import ( "fmt" "time" ) func main() { // 创建两个通道 ch:=make(chan string,100) ch2:=make(chan string,100) // 接收ch通道数据的协程 go func() { for { s:=<-ch fmt.Printf("接收数据:%s\n",s) } }() // 向ch2通道写入数据的协程 go func() { for { ch2 <- "新的通道" time.Sleep(1*time.Second) } }() // 计数 num:=0 for { select { case ch <- "第1个case写入": case ch <- "第2个case写入": case s:= <- ch2: num++ fmt.Printf("第3个case:%s,第%d次\n",s,num) } time.Sleep(1*time.Second) } }
上面代码中,我们用前两个case
来向ch
通道里写入数据,第三个case
来向ch2
通道里读取数据。同时由于写操作总是会被执行,所以select
不会被阻塞,也就不需要default
了。我们加了一个num
变量,以便理解select
每次只执行一个case
的效果。该程序最终会输出:
接收数据:第1个case写入 接收数据:第1个case写入 第3个case:新的通道,第1次 接收数据:第1个case写入 接收数据:第2个case写入 第3个case:新的通道,第2次 接收数据:第2个case写入 ......
总结
通过上面的几个例子我们可以得出select
的几个特点:
- 当
case
里的表达式是向channel
里写数据时,这个case
总是会被执行 - 当有多个
case
可以被执行时,会随机执行其中一条 - 每次只能有一个
case
被执行 - 当没有
case
可执行时,default
会被执行 - 当没有
case
可执行时,同时也没有default
时,select
会被阻塞。