偶然看到这样一篇文章:《使用 pprof 排查 Golang 内存泄露》https://www.toutiao.com/i6881796351139676680/
最后一段让我很疑惑:
修改 for ... select ... time.After 造成的内存泄露
原来程序中存在如下代码:
for {
select {
case a := <-chanA:
... case b := <-chanB:
.... case <-time.After(20*time.Minutes):
return nil, errors.New("download timeout")
}
time.After 就是封装了一层的 NewTimer, time.After 的源码:
func After(d Duration) <-chan Time {
return NewTimer(d).C
}
修复该错误, 只调用一次 NewTimer:
downloadTimeout := time.NewTimer(20 * time.Minute)
defer downloadTimeout.Stop()
for {
select {
case a := <-chanA:
... case b := <-chanB:
.... case <-downloadTimeout.C:
return nil, errors.New("download timeout")
}
连官网的例子都是:case <-time.After(xxx), 为什么这里就出现内存泄露了?
感谢yifhao同学耐心细致的讲解:
- 20 * time.Minute 这个时间太长了
- 如果1s钟调用这里1000次, 那就可能会同时存在20601000个定时事件
- 退出select的作用域的时候,time.After(20*time.Minutes)产生的对象不会被GC,走到runtime里的timer调度去了
- 退出作用域不会被释放. 定时事件作用域在timer的调度器上去了。定时器是全局对象. 调度到了才被释放。
- 因此,时间短,且循环不频繁的情况下,case <-time.After(xxx)这个写法还是可以的