Golang 中 Timer 的陷阱
Golang 的 Timer 类,是一个普遍意义上的定时器,它有着普通定时器的一些特性,例如:
- 给定一个到期时间,和一个回调函数,到期后会调用回调函数
- 重置定时器的超时时间
- 停止定时器
Golang 的 Timer 在源码中,实现的方式是以一个小顶堆来维护所有的 Timer 集合。接着启动一个独立的 goroutine,循环从小顶堆中的检测最近一个到期的 Timer 的到期时间,接着它睡眠到最近一个定时器到期的时间。最后会执行开始时设定的回调函数。Timer 到期之后,会被 Golang 的 runtime 从小项堆中删除,并等待 GC 回收资源。
1 | package main |
timer.NewTimer()
会启动一个新的 Timer 实例,并开始计时。 我们启动一个新的 goroutine,来以阻塞的方式从 Timer 的 C 这个 channel 中,等待接收一个值,这个值是到期的时间。并打印”Timer has expired.”
到现在看起来似乎没什么问题,但是当我们执行 timer.Stop()
之后,3 秒钟过去了,程序却没有打印那句话。说明执行 timer.Stop()
之后,Timer 自带的 channel 并没有关闭,而且这个 Timer 已经从 runtime 中删除了,所以这个 Timer 永远不会到期。
这会导致程序逻辑错误,或者更严重的导致 goroutine 和内存泄露。解决的办法是,使用 timer.Reset()
代替 timer.Stop()
来停止定时器。
1 | package main |
这样做就相当于给 Timer 一个 0 秒的超时时间,让 Timer 立刻过期。