定时器在Go语言应用中使用非常广泛,Go语言的标准库里提供两种类型的计时器,一种是一次性的定时器Timer
,另外一种是周期性的定时器Ticker
。本文主要来看一下Ticker
的用法和实现原理。
Ticker是周期性定时器,即周期性的触发一个事件,它会以一个间隔(interval)往channel发送一个事件(当前时间),而channel的接收者可以以固定的时间间隔从channel中读取事件。通过Ticker本身提供的管道将事件传递出去。
package main
import (
"fmt"
"time"
)
func main() {
ticker := time.NewTicker(time.Second * 1) //创建一个周期性定时器
i := 1
for {
fmt.Println(i, "====>", <-ticker.C)
if i == 5 {
ticker.Stop() //停止定时器
break
}
i++
}
}
输出结果:
1 ====> 2022-08-24 15:58:38.971837 +0800 CST m=+1.001366085
2 ====> 2022-08-24 15:58:39.971154 +0800 CST m=+2.000695418
3 ====> 2022-08-24 15:58:40.971633 +0800 CST m=+3.001185460
4 ====> 2022-08-24 15:58:41.97109 +0800 CST m=+4.000654126
5 ====> 2022-08-24 15:58:42.971594 +0800 CST m=+5.001169210
使用NewTicker()方法就可以创建一个周期性定时器,函数如下:
func NewTicker(d Duration) *Ticker
其中参数d即为定时器时间触发的周期,为一个时间段。
使用定时器对外暴露的 Stop 方法就可以停掉一个周期性定时器, 函数如下:
func (t *Ticker) Stop()
该方法会停止计时,停止后不会向定时器的管道中写入事件,但管道并不会被关闭。管道在使用完成后,生命周期结束后会自动释放。
Ticker的数据结构与Timer完全一致:
通过src/time.sleep.go:Ticker
定义了Timer
数据结构:
type Ticker struct {
C <-chan Time
r runtimeTimer
}
它提供了一个channel
,在定时时间到达之前,没有数据写入Ticker.C
会一直阻塞,直到时间到达,向channel
写入系统时间,阻塞解除,可以从中读取数据,这就是一个事件。
我们可以理解为Ticker.C
即面向Ticker
用户的,Ticker.r
是面向底层的定时器实现。
runtimeTimer与Timer一样,任务的载体,用于监控定时任务,每创建一个Timer就创建一个runtimeTimer变量,然后把它交给系统进行监控,我们通过设置runtimeTimer过期后的行为来达到定时的目的。
源码包src/time/sleep.go:runtimeTimer定义了其数据结构:
type runtimeTimer struct {
tb uintptr // 存储当前定时器的数组地址
i int // 存储当前定时器的数组下标
when int64 // 当前定时器触发时间
period int64 // 当前定时器周期触发间隔
f func(interface{}, uintptr) // 定时器触发时执行的函数
arg interface{} // 定时器触发时执行函数传递的参数一
seq uintptr // 定时器触发时执行函数传递的参数二(该参数只在网络收发场景下使用)
}
func NewTicker(d Duration) *Ticker {
if d <= 0 {
panic(errors.New("non-positive interval for NewTicker"))
}
// Give the channel a 1-element time buffer.
// If the client falls behind while reading, we drop ticks
// on the floor until the client catches up.
c := make(chan Time, 1)
t := &Ticker{
C: c,
r: runtimeTimer{
when: when(d),
period: int64(d), // Ticker跟Timer的重要区就是提供了period这个参数,据此决定timer是一次性的,还是周期性的
f: sendTime,
arg: c,
},
}
startTimer(&t.r)
return t
}
NewTicker()构造了一个Ticker,然后把Ticker.r通过startTimer()交给系统协程维护。
其中period为事件触发的周期。
停止Ticker,只是把Ticker从系统协程中移除。
func (t *Ticker) Stop() {
stopTimer(&t.r)
}
stopTicker()即通知系统协程把该Ticker移除,即不再监控。系统协程只是移除Ticker并不会关闭管道,以避免用户协程读取错误。
Ticker在使用完后务必要释放,否则会产生资源泄露,进而会持续消耗CPU资源,最后会把CPU耗尽。
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
Ticker相关内容总结如下: