 Go 中的 select 提供了方便的多路复用功能,这里记录几个常见的”坑”。
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版本终于把这个问题修正了,系统做了特殊处理,及时释放