首发于微信公众号:【码农在新加坡】,欢迎关注。

背景

我刚转做go语言开发开始写入职小程序的时候,写下了如下的代码:

从连接池取连接

说实话,我觉得我的代码可优雅了,后来对go语言有了更深入的了解之后,发现我写的代码有着明显的内存泄漏的问题。

time.After坑
time.After

我们来用最简单的例子模拟一下:

for select case

我相信大家一眼就能看懂。就是这么简单的代码却会导致内存泄漏。

top
MEM

原理分析

time packagetime.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.

该方法可以在一定时间(根据所传入的 Duration)后主动返回 time.Time 类型的 channel 消息。

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

回过头来看我们的代码,这里我们的定时时间设置的是3分钟, 在for循环内每次select的时候,都会实例化一个一个新的定时器。该定时器在3分钟后,才会被激活,但是激活后已经跟select无引用关系,被gc给清理掉。

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

解决方案

time.Afterfor select casetime.After
time packageNewTimer
time.Aftertime.Reset()
NewTimertime.Reset
NewTimer creates a new Timer that will send the current time on its channel after at least duration d.
Timer
The Timer type represents a single event. When the Timer expires, the current time will be sent on C, unless the Timer was created by AfterFunc. A Timer must be created with NewTimer or AfterFunc.
time.NewTimerTimerReset

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

这么改之后,再次测试代码,发现内存一直稳定。

总结

for select casetime.After
time.NewTimerReset

<全文完>

欢迎关注我的微信公众号:码农在新加坡,有更多好的技术分享。

个人博客网站: