Go语言time.After内存泄漏分析

278 阅读1分钟

有天在写代码的时候,正好在写一个定时任务把查询信息添加到redis,被组长臭骂一顿,你就这样写代码的??? 我打包放在服务器中运行测试一下发现内存疯狂激增 源代码

for {
    select {
        case v := <- chs:
            fmt.Printf("print:%v\n", v) 
        case <- time.After(c.Timeouot):
            ... //get info to add redis
    }
}

就是这么简单的代码却会导致内存泄漏。 点击time.After查看源码,发现以下注释------

// After waits for the duration to elapse and then sends the current time
// on the returned channel.
// It is equivalent to NewTimer(d).C.
// The underlying Timer is not recovered by the garbage collector
// until the timer fires. If efficiency is a concern, use NewTimer
// instead and call Timer.Stop if the timer is no longer needed.

翻译一下:在计时器触发之前,垃圾收集器不会回收Timer - 如果考虑效率,需要使用NewTimer替代

在select里面虽然我们没有执行到time.After,但是这个对象已经初始化了,依然在时间堆里面,定时任务未到期之前,是不会被gc清理的。

time.After虽然调用的是timer定时器,但是他没有使用time.Reset()方法再次激活定时器,所以每一次都是新创建的实例,才会造成的内存泄漏

** 解决方案** 我们使用NewTimer创建定时器,再加上time.Reset每次重新激活定时器,即可完美解决问题。

func Add() {
    delay := time.NewTimer(3 * time.Minute)

    defer delay.Stop()

    for {
        delay.Reset(3 * time.Minute)

        select {
            case v := <- chs:
                fmt.Printf("print:%v\n", v)
            case <- delay.C:
                 ... //get info to add redis
        }
    }
}
```
```


//记得一定要使用Reset重置定时器,如果不重置,那么定时器还是从创建的时候开始计算时间流逝。
//使用了Reset之后,每次都从当前Reset的时间开始算。