有天在写代码的时候,正好在写一个定时任务把查询信息添加到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。