简介
在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会被阻塞。