【涨姿势】原来golang的case <-time.After(xxx)还有这样的坑

偶然看到这样一篇文章:《使用 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)这个写法还是可以的

posted on 2020-10-12 21:14  ahfuzhang  阅读(1057)  评论(0编辑  收藏  举报