有天在写代码的时候,正好在写一个定时任务把查询信息添加到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.Aftertime.Reset()
NewTimertime.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的时间开始算。

总结

在for selectcase里面使用定时器一定要小心,定时器只有等到计时器触发之后才会被垃圾回收器回收,而time.After是单次触发且无法Reset。

这种需要循环内触发定时器的用例,还是要使用time.NewTimer并手动Reset。