Golang的Timer类,是一个普遍意义上的定时器,它有着普通定时器的一些特性,例如:
- 给定一个到期时间,和一个回调函数,到期后会调用回调函数
- 重置定时器的超时时间
- 停止定时器
Golang的Timer在源码中,实现的方式是以一个小顶堆来维护所有的Timer集合。接着启动一个独立的goroutine,循环从小顶堆中的检测最近一个到期的Timer的到期时间,接着它睡眠到最近一个定时器到期的时间。最后会执行开始时设定的回调函数。Timer到期之后,会被Golang的runtime从小项堆中删除,并等待GC回收资源。
下面给出实际的代码:
package main
import (
"time"
"fmt"
)
func main() {
timer := time.NewTimer(3 * time.Second)
go func() {
<-timer.C
fmt.Println("Timer has expired.")
}()
timer.Stop()
time.Sleep(60 * time.Second)
}
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()
来停止定时器。
package main
import (
"time"
"fmt"
)
func main() {
timer := time.NewTimer(3 * time.Second)
go func() {
<-timer.C
fmt.Println("Timer has expired.")
}()
//timer.Stop()
timer.Reset(0 * time.Second)
time.Sleep(60 * time.Second)
}
这样做就相当于给Timer一个0秒的超时时间,让Timer立刻过期。
有疑问加站长微信联系(非本文作者)