目录
1、背景
之前讲过Ticker定时器,每隔一段时间往通道写入当前时间。time包中还提供了另一种定时器:Timer,与Ticker不同的是,Timer只会执行一次,但是也有办法定时间隔执行,下面我们来大概理解一下其实现原理。
2、go版本
$ go version
go version go1.21.4 windows/386
3、源码解释
【1】Timer结构
Timer底层结构如下:
go
type Timer struct {
C <-chan Time //只读的时间通道
r runtimeTimer //底层定时结构
}
runtimeTimer结构如下:
go
type runtimeTimer struct {
pp uintptr
when int64 //什么时候执行任务
period int64 //与Ticker的区别就是没用此字段
f func(any, uintptr)
arg any
seq uintptr
nextwhen int64
status uint32
}
Timer结构和Ticker一模一样,没什么区别。
【2】NewTimer函数解释
初始化一个Timer对象用的函数是NewTimer,源码如下:
go
func NewTimer(d Duration) *Timer {
c := make(chan Time, 1)
t := &Timer{
C: c,
r: runtimeTimer{
when: when(d),
f: sendTime,
arg: c,
},
}
startTimer(&t.r)
return t
}
看的出来与Ticker的唯一区别就是没有赋值period字段,所以不会像Ticker一样每隔一段时间往通道写数据,这就是Timer只会往通道写一次数据的原因。
【3】After和AfterFunc函数解释
TImer定时器我们经常使用的2个函数就是After和AfterFunc,源码如下:
go
func After(d Duration) <-chan Time {
return NewTimer(d).C
}
func AfterFunc(d Duration, f func()) *Timer {
t := &Timer{
r: runtimeTimer{
when: when(d),
f: goFunc,
arg: f,
},
}
startTimer(&t.r)
return t
}
能看出来After函数就是将TImer中的只读通道作为返回值,我们能直接从这个通道中读取执行一次任务的时间,AfterFunc就是到达指定时间之后执行我们自定义的函数。
4、Timer定时间隔执行任务
利用Timer对象提供的Reset函数就可以实现定时执行任务的功能,示例代码如下:
go
func main() {
timer := time.NewTimer(5 * time.Second)
for {
t := <-timer.C
logger.Info("exec task", zap.Time("t", t))
timer.Reset(5 * time.Second)
}
}
控制台输出:
shell
[2024-12-01 13:37:36.778] | INFO | Goroutine:1 | [chan_demo/main.go:110] | exec task | {"t": "[2024-12-01 13:37:36.778]"}
[2024-12-01 13:37:41.830] | INFO | Goroutine:1 | [chan_demo/main.go:110] | exec task | {"t": "[2024-12-01 13:37:41.830]"}
[2024-12-01 13:37:46.833] | INFO | Goroutine:1 | [chan_demo/main.go:110] | exec task | {"t": "[2024-12-01 13:37:46.833]"}
[2024-12-01 13:37:51.842] | INFO | Goroutine:1 | [chan_demo/main.go:110] | exec task | {"t": "[2024-12-01 13:37:51.842]"}
可以看到每隔5秒执行一次,需要注意的是,在循环里一直调用time.After也能实现定时间隔的任务,但是After函数内部会初始化一个Timer对象,每次循环都会创建一个Timer对象,增加了垃圾回收的开销,所以用Rest函数只用创建一个对象,推荐使用这种方式。
5、总结
通过看Timer的源码之后,能更好的根据不同的业务场景去使用不同的定时器,对于不满足业务场景的定时器,可以采用工厂模式的方式封装一个自定义的定时器去满足我们的业务使用。