select 是操作系统中的系统调用,我们经常会使用 select、poll 和 epoll 等函数构建 I/O 多路复用模型提升程序的性能。在 Go 中当涉及到对多个 channel 进行操作时,通过 select 我们可以同时在多个 channel 上进行发送/接收操作。select 的用法和 switch 比较类似,但是切记 select 的每个 case 是通信(这里的通信指 channel 的接收或者发送)。
一、基本用法
select {
case x := <-c1: // 从channel c1接收数据
case y, ok := <-c2: // 从channel c2接收数据,并根据ok值判断c2是否已经关闭
case c3 <- z: // 将z值发送到channel c3中:
default: // 当上面case中的channel通信均无法实施时,执行该默认分支
}
当程序执行到 select 顶部时,它会评估 case 内部可用于通信的所有 channel,然后阻塞,直到其中一个可以运行为止。一旦其中一个可以运行,对应的 case 就会执行。如果没有 default,select 将永远阻塞直到其中一个 case 的 channel 可以通信。如果有default,且所有 case 中的 channel 都无法通信,则执行default 分支,避免阻塞。
二、select 常见用法:
1.利用 default 分支避免阻塞
当 select 语句中没有可执行的 case 分支时,select 会一直阻塞。此时 select 语句中如果有 default 分支,则可以执行 default 分支语句,从而避免了阻塞。
2.实现超时机制
通过超时事件,我们既可以避免长期陷入某种操作的等待中,也可以做一些异常处理工作。下面示例代码中,如果 <- c 5秒还无法执行,则直接进入超时流程。
func worker() {
select {
case <-c:
// do something
case <-time.After(5 *time.Second):
fmt.Println("timeout")
return
}
}
3.实现心跳机制
使用 time 包的 Ticker,我们可以实现带有心跳机制的 select。使得我们可以在监听 channel 的同时,执行一些周期性的任务:
func worker() {
heartbeat := time.NewTicker(1 * time.Second)
defer heartbeat.Stop()
for {
select {
case <-c:
// do something
case <- heartbeat.C:
// do something
}
}
}
4.阻塞 main 函数
func main() {
bufChan := make(chan int)
go func() {
for {
bufChan <- 1
time.Sleep(time.Second)
}
}()
go func() {
for {
fmt.Println(<-bufChan)
}
}()
select {}
}
上面使用 select ,main 函数就永远阻塞住了。这里要注意上面一定要有一直活动的goroutine否则会报deadlock。