Go的select功能和C中的select, poll, epoll类似,就是监听 IO 操作,当 IO 操作发生时,触发相应的动作。
在某些场景下,我们需要同时从多个通道接收数据。通道在接收数据时,如果没有数据可以接收将会发生阻塞。
使用 select 类似于 switch 语句,每个case会对应一个通道的通信(接收或发送)过程,但是它的 case 涉及到 channel 有关的 I/O 操作或者说 select 就是用来监听和 channel 有关的 IO 操作,当 IO 操作发生时,触发相应的动作。
// 基本用法
select {
case <- ch1:
// 如果ch1成功读到数据,则进行该case处理语句
case ch2 <- 1:
// 如果成功向ch2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程
}
案例1:
package main
import "fmt"
func main() {
c, quit := make(chan int), make(chan int)
go func() {
c <- 2 // 添加数据
quit <- 1 // 发送完成信号
} ()
for isQuit := false; !isQuit; {
// 监视信道c的数据流出
select {
case v := <-c: fmt.Printf("received %d from c", v)
case <-quit: isQuit = true
// quit信道有输出,关闭for循环
}
}
}
案例2:
实现timeout机制
package main
import (
"fmt"
"time"
)
func main() {
timeOut := make(chan int)
ch := make(chan int)
go func() {
time.Sleep(1e9)
timeOut <- 1
}()
select {
case <-ch:
fmt.Println("c is ")
case a := <-timeOut:
fmt.Println("timeout", a)
}
}
同时等待读取ch和timeOut,当超时时间有数据时候,<- timeout会执行,即 select 语句会退出。 而不是一直阻塞在 ch 的读取操作上。 从而实现了对 ch 读取操作的超时设置。
如果select包含default,则上述两个都无法成功读取的情况下,直接进入default。
案例3:
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan int)
go s1(ch1)
for {
select {
case c1 := <- ch1:
fmt.Println("c1 is", c1)
case <- time.After(time.Second * 2):
fmt.Println("timeout: 2s")
return
}
}
}
func s1(ch chan int) {
for i:=0; i<100; i++ {
ch <- i
time.Sleep(3 * time.Second)
}
}
switch和select区别:
select可处理一个或多个channel的发送/接收操作。
如果多个case同时满足,select会随机选择一个。
对于没有case的select,会一直等待,可用于阻塞main函数。
switch可以是各种类型,select 只能是IO操作(通常是channel)。
switch是顺序执行的。
switch中的default是默认的意思,当所有case不满足的时候,就会执行default。
select中的default是当select发现没有case满足,要block时的选择。