select 是操作系统中的系统调用,我们经常会使用 select、poll 和 epoll 等函数构建 I/O 多路复用模型提升程序的性能。Go 语言的 select 与操作系统中的 select 比较相似,Go 语言中的 select 也能够让 Goroutine 同时等待多个 Channel 可读或者可写,在多个文件或者 Channel状态改变之前,select 会一直阻塞当前线程或者 Goroutine。
select {
case <-chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
}
select随机选择机制
select 在遇到多个 <-ch 同时满足可读或者可写条件时会随机选择一个 case 执行其中的代码。
func main() {
ch := make(chan int)
go func() {
for range time.Tick(1 * time.Second) {
ch <- 0
}
}()
for {
select {
case <-ch:
println("case1")
case <-ch:
println("case2")
}
}
}
$ go run main.go
case1
case2
case1
...
非阻塞的收发
在通常情况下,select语句会阻塞当前 Goroutine 并等待多个 Channel 中的一个达到可以收发的状态。但是如果 select 控制结构中包含 default 语句,那么这个 elect
语句在执行时会遇到以下两种情况:
- 当存在可以收发的 Channel 时,直接处理该 Channel 对应的 case;
- 当不存在可以收发的 Channel 时,执行 default中的语句;
当我们运行下面的代码时就不会阻塞当前的 Goroutine,它会直接执行 default 中的代码。
func main() {
ch := make(chan int)
select {
case i := <-ch:
println(i)
default:
println("default")
}
}
$ go run main.go
default
select超时控制
带超时机制的 select,是 Go 中常见的一种 select 和 channel 的组合用法。通过超时事件,我们既可以避免长期陷入某种操作的等待中,也可以做一些异常处理工作。比如,下面示例代码实现了一次具有 30s 超时的 select:
func worker() {
select {
case <-c:
// ... do some stuff
case <-time.After(30 *time.Second):
return
}
}
在应用带有超时机制的 select 时,我们要特别注意 timer 使用后的释放,尤其在大量创建 timer 的时候。
select实现心跳机制
结合 time 包的 Ticker,我们可以实现带有心跳机制的 select。这种机制让我们可以在监听 channel 的同时,执行一些周期性的任务
func worker() {
heartbeat := time.NewTicker(30 * time.Second)
defer heartbeat.Stop()
for {
select {
case <-c:
// ... do some stuff
case <- heartbeat.C:
//... do heartbeat stuff
}
}
}
参考资料
- 《Go 语言第一课》
- 《Go语言底层原理剖析》
- 《Go语言精进之路》
- 《Go语言圣经》
- 《Go 语言设计与实现》