Go 中的 select 提供了方便的多路复用功能,这里记录几个常见的”坑”。
break 匹配问题
select 的每个 case (包括 default)会隐含自动使用 break 语句, 所以在其中使用 break 语句会被 select”吸收”而不会传到到外层的 for 循环,需要用 break label 解决
// 错误示例
for {
select {
case <-ch1:
break // 这个 break 无法跳出 for
}
}
// 正确示例
outer:
for {
select {
case <-ch1:
break outer // 可以跳出 for 循环
}
}
timer.After 泄漏问题
// 错误示例
for {
select {
case <-ch:
// do something...
case <-time.After(3 * time.Minute): // 高并发下被不断执行
// timeout
// 这里如果不小心加上 default,会直接导致 OOM
}
}
上面的代码在ch
不断接收数据时会导致内存暴增。
原因是每次执行select
时都会执行一次<-time.After(3 * time.Minute)
创建一个 timer,
这个 timer 在 ch 数据到来后实际上没有用了,但是没有及释放,产生了积压
下面的方案用一个 timer 对象就实现了相同功能,并且不会造成 timer 泄漏
// 解决方案
func test(ch chan struct{}) {
const TimeoutInterval = 3 * time.Minute
ticker := time.NewTimer(TimeoutInterval)
defer ticker.Stop()
...
for {
select {
case <-ch:
// do something...
ticker.Reset(TimeoutInterval)
case <-ticker.C:
// timeout
}
}
}
- 解决泄漏的方法是用 time.ticker 时及时 stop
- 另外在1.23版本终于把这个问题修正了,系统做了特殊处理,及时释放
time.After()
被阻塞的线程
- 另外在1.23版本终于把这个问题修正了,系统做了特殊处理,及时释放