Go语言提供了两种定时器.分别为一次性定时器和周期性定时器.
一次性定时器(Timer):定时器只计时一次.计时结束便停止运行.
周期性定时器(Ticker):定时器周期性的进行计时.除非主动停止.否则将永久运行.
1.一次性定时器(Timer):
Timer是一种单一事件的定时器.即经过指定时间后触发一个事件.这个事件通过其本
身提供的channel进行通知.之所以叫单一事件.是因为Timer只执行一次就结束.这也
是Timer和Ticker最重要的区别.
通过timer.NewTimer(d Duration)可以创建一个Timer.参数即等待的时间.时间
到来后立即触发一个事件.
源码位置src/time/sleep.go:Timer:
// Note: The runtime knows the layout of struct Timer, since newTimer allocates it.
// The runtime also knows that Ticker and Timer have the same layout.
// There are extra fields after the channel, reserved for the runtime
// and inaccessible to users.
// 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.
type Timer struct {
C <-chan Time
initTimer bool
}
Timer对外仅暴露一个channel.指定的时间到来时就往该channel中写入系统时间.
即一个事件.
2.使用时间:
1).设定超时时间:
协程从管道读取数据时.如果管道没有数据.那么协程将被阻塞.一直等待管道中有新的
数据写入.有时我们不希望协程永久阻塞.而是等待一个指定时间.如果此时间段呢管道
中仍没有新数据.则协程可以判定为超时.并转而去处理其他逻辑.
示例:
func WaitChannel(conn <-chan string) bool {
timer := time.NewTimer(1 * time.Second)
select {
case <-conn:
timer.Stop()
return true
case <-timer.C: //超时
fmt.Println("timeout")
return false
}
}
WaitChannel的作用就是检测指定的管道中是否有数据到来.通过select语句轮询
conn和timer.C两个管道.timer会在一秒内向timer.C写入数据.如果1s内conn还没
有数据.则判断为超时.
2).延迟执行某个方法:
有时我们希望某个方法在今后的某个时刻执行.示例如下:
func DelayFunction() {
timer := time.NewTimer(5 * time.Second)
select {
case <-timer.C:
log.Print("delayed 5s")
}
}
会一直等待timer的事件到来后才会执行后面的方法.
3.Timer对外接口:
1).创建定时器:
*使用func NewTimer(d Duration) Timer方法指定一个时间即可创建一个
Timer.Timer一经创建便开始计时.不需要额外的启动命令.
func NewTimer(d Duration) *Timer {
c := make(chan Time, 1)
t := (*Timer)(newTimer(when(d), 0, sendTime, c, syncTimer(c)))
t.C = c
return t
}
2).停止定时器:
Timer创建后可以随时停止.停止计时器的方法如下:
*func(t Timer) Stop() bool
func (t *Timer) Stop() bool {
if !t.initTimer {
panic("time: Stop called on uninitialized Timer")
}
return stopTimer(t)
}
返回值代表定时器有没有超时.
true:定时器超时前停止.后续不会在发送事件.
false:定时器超时后停止.
3).重置定时器:
已过期的定时器或已经停止的定时器可以通过重置动作重新激活.重置方法如下.
*func (t Timer) Reset(d Duration)bool
func (t *Timer) Reset(d Duration) bool {
if !t.initTimer {
panic("time: Reset called on uninitialized Timer")
}
w := when(d)
return resetTimer(t, w, 0)
}
重置的动作实质上是先停止定时器.在启动.其返回值即停止器(Stop())的返回值.需要
注意的是重置定时器虽然可以用于修改还未超时的定时器.但正确的使用方式还是针
对已过期的定时器或已经停止的定时器同时其返回值也不可靠.返回值存在的价值仅
仅是与前面的版本兼容.
3.简单接口:
1).After():
有时我们想等待指定的时间.没有提前停止定时器的需求.也没有复用该定时器的需要.
那么可以使用匿名定时器.
使用func After(d Duration) <-chan Time 方法创建一个定时器.并返回定时器
的管道.示例如下:
func AfterDemo() {
log.Println(time.Now())
<-time.After(1 * time.Second)
log.Println(time.Now())
}
2)AfterFunc():
通过AfterFunc可以自定义执行一个方法.原型为:
*func AfterFunc(d Duration,f func()) Timer.
示例:
func AfterFuncDemo() {
log.Println("AfterFuncDemo")
time.AfterFunc(1*time.Second, func() {
log.Println("AfterFuncDemo")
})
time.Sleep(2 * time.Second)
}
4.实现原理:
1).数据结构:
Timer:
源码位置:src/time/sleep.go:Timer
type Timer struct {
C <-chan Time
initTimer bool
}
C:管道.上层应用根据此管道接收事件.
initTimer:是否初始化Timer.
2.)创建Timer:
源码位置:src/time/sleep.go:NewTimer
// These functions are pushed to package time from package runtime.
// The arg cp is a chan Time, but the declaration in runtime uses a pointer,
// so we use a pointer here too. This keeps some tools that aggressively
// compare linknamed symbol definitions happier.
//
//go:linkname newTimer
func newTimer(when, period int64, f func(any, uintptr, int64), arg any, cp unsafe.Pointer) *Timer
可以看到这里只有方法声明没有实现.这个函数会在运行的时候进行编译.
源码位置:src/runtime/time.go:newTimer
// newTimer allocates and returns a new time.Timer or time.Ticker (same layout)
// with the given parameters.
//
//go:linkname newTimer time.newTimer
func newTimer(when, period int64, f func(arg any, seq uintptr, delay int64), arg any, c *hchan) *timeTimer {
t := new(timeTimer)
t.timer.init(nil, nil)
t.trace("new")
if raceenabled {
racerelease(unsafe.Pointer(&t.timer))
}
if c != nil {
lockInit(&t.sendLock, lockRankTimerSend)
t.isChan = true
c.timer = &t.timer
if c.dataqsiz == 0 {
throw("invalid timer channel: no capacity")
}
}
if gr := getg().syncGroup; gr != nil {
t.isFake = true
}
t.modify(when, period, f, arg, 0)
t.init = true
return t
}
从源码可以看出.就是构造了一个timeTimer.对timer进行初始化.然后modift方法
会将定时器加入运行的定时器堆.
t.timer.init()方法:
func (t *timer) init(f func(arg any, seq uintptr, delay int64), arg any) {
lockInit(&t.mu, lockRankTimer)
t.f = f
t.arg = arg
}
这一步就是初始化了一个锁.因为传入的值都为nil.
t.modify()方法:
func (t *timer) modify(when, period int64, f func(arg any, seq uintptr, delay int64), arg any, seq uintptr) bool {
if when <= 0 {
throw("timer when must be positive")
}
if period < 0 {
throw("timer period must be non-negative")
}
async := debug.asynctimerchan.Load() != 0
if !async && t.isChan {
lock(&t.sendLock)
}
t.lock()
if async {
t.maybeRunAsync()
}
t.trace("modify")
oldPeriod := t.period
t.period = period
if f != nil {
t.f = f
t.arg = arg
t.seq = seq
}
wake := false
pending := t.when > 0
t.when = when
if t.state&timerHeaped != 0 {
t.state |= timerModified
if t.state&timerZombie != 0 {
// In the heap but marked for removal (by a Stop).
// Unmark it, since it has been Reset and will be running again.
t.ts.zombies.Add(-1)
t.state &^= timerZombie
}
// The corresponding heap[i].when is updated later.
// See comment in type timer above and in timers.adjust below.
if min := t.ts.minWhenModified.Load(); min == 0 || when < min {
wake = true
// Force timerModified bit out to t.astate before updating t.minWhenModified,
// to synchronize with t.ts.adjust. See comment in adjust.
t.astate.Store(t.state)
t.ts.updateMinWhenModified(when)
}
}
add := t.needsAdd()
if !async && t.isChan {
// Stop any future sends with stale values.
// See timer.unlockAndRun.
t.seq++
// If there is currently a send in progress,
// incrementing seq is going to prevent that
// send from actually happening. That means
// that we should return true: the timer was
// stopped, even though t.when may be zero.
if oldPeriod == 0 && t.isSending.Load() > 0 {
pending = true
}
}
t.unlock()
if !async && t.isChan {
if timerchandrain(t.hchan()) {
pending = true
}
unlock(&t.sendLock)
}
if add {
t.maybeAdd()
}
if wake {
wakeNetPoller(when)
}
return pending
}
when:定时器首次触发的时间.
period:定时器重新触发间隔.
f:定时器触发的回调函数.
arg:传给回调函数的参数.
c:关联通道.触发时向通道发送时间事件.
t.modify()方法解释:
定时器触发事件必须大于0.周期也必须大于0.否则就panic.
对于关键值的修改.通过加锁来保证并发下的安全.
timerHeaped :定时器状态位,表示该定时器已加入「定时器堆.
timerModified :标记定时器的触发时间 / 周期已修改,通知 timerproc 重新调整堆;
timerZombie :标记定时器为 “僵尸”(待从堆中移除),通常由 Stop() 触发;
t.ts :定时器所属的 timersBucket (定时器桶),Go 用分段桶减少锁竞争;
minWhenModified *:定时器桶的最小触发时间, *timerproc 会根据这个值决定休眠多久,若新 when 更小,需要唤醒 timerproc 重新计算休眠时间。
通道类型定时器的并发保护:
t.seq:序列号.用于防止过期的定时器触发事件写入通道.(比如定时器被reset之后.旧
的触发逻辑会因为seq不匹配而放弃发送).
t.isSending:用于标记是否有向通道发送数据的操作在进行.保证并发下的状态正确.
通道数据清理与锁释放:
timerchandrain :清空通道中过期的定时器事件,避免用户读取到旧数据
定时器入堆与唤醒协程:
t.mayveAdd:将定时器加入timersBucket的堆中.
wakeNetPoller:唤醒网络轮询器.
返回pending:告知上层是否有未完成触发的任务.
流程图:
3).停止Timer:
源码位置:src/time/sleep.go:stopTimer()
//go:linkname stopTimer
func stopTimer(*Timer) bool
和上面原因一样.实现位置.
源码位置:src/runtime/time.go:stopTimer
// stopTimer stops a timer.
// It reports whether t was stopped before being run.
//
//go:linkname stopTimer time.stopTimer
func stopTimer(t *timeTimer) bool {
if t.isFake && getg().syncGroup == nil {
panic("stop of synctest timer from outside bubble")
}
return t.stop()
}
timeTimer:是运行时的内部定时器结构体.
isFake:timeTimer的字段.标记该定时器为伪定时器.
getg():是获取当前goroutine结构体函数.syncGroup是goroutine关联的同步测
试组.
方法解释:
1).核心校验:
2).t.stop()方法:
源码如下:
func (t *timer) stop() bool {
async := debug.asynctimerchan.Load() != 0
if !async && t.isChan {
lock(&t.sendLock)
}
t.lock()
t.trace("stop")
if async {
t.maybeRunAsync()
}
if t.state&timerHeaped != 0 {
t.state |= timerModified
if t.state&timerZombie == 0 {
t.state |= timerZombie
t.ts.zombies.Add(1)
}
}
pending := t.when > 0
t.when = 0
if !async && t.isChan {
// Stop any future sends with stale values.
// See timer.unlockAndRun.
t.seq++
// If there is currently a send in progress,
// incrementing seq is going to prevent that
// send from actually happening. That means
// that we should return true: the timer was
// stopped, even though t.when may be zero.
if t.period == 0 && t.isSending.Load() > 0 {
pending = true
}
}
t.unlock()
if !async && t.isChan {
unlock(&t.sendLock)
if timerchandrain(t.hchan()) {
pending = true
}
}
return pending
}
1.异步模式判断与通道锁:
2.标记定时器状态:
timerHeaped:定时器已经加入堆中.需要移除.
timerModified:定时器状态已修改.需要协程重新检查调整定时器堆.
timerZombie:标记位待移除状态.会在下次轮询中移除.
3.通道类型定时器,防止过期数据发送:
4.释放锁.并清理通道数据:
5.流程梳理:
4).重置Timer:
源码位置:src/time/sleep.go:resetTimer:
//go:linkname resetTimer
func resetTimer(t *Timer, when, period int64) bool
实现位置:src/runtime/time.go:restTimer:
// resetTimer resets an inactive timer, adding it to the timer heap.
//
// Reports whether the timer was modified before it was run.
//
//go:linkname resetTimer time.resetTimer
func resetTimer(t *timeTimer, when, period int64) bool {
if raceenabled {
racerelease(unsafe.Pointer(&t.timer))
}
if t.isFake && getg().syncGroup == nil {
panic("reset of synctest timer from outside bubble")
}
return t.reset(when, period)
}
上面源码可以看到关键方法在reset()方法中.
源码如下:
// reset resets the time when a timer should fire.
// If used for an inactive timer, the timer will become active.
// Reports whether the timer was active and was stopped.
func (t *timer) reset(when, period int64) bool {
return t.modify(when, period, nil, nil, 0)
}
可以看到内部调用了modify方法.传入了新的when值和间隔执行值.(modify方法可以参考上面).
天亮以后.会在嘛.
*如果大家喜欢我的分享的话. *可以关注我的微信公众号
念何架构之路