Go进阶之定时器Timer

34 阅读8分钟

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()方法解释:

image.png

定时器触发事件必须大于0.周期也必须大于0.否则就panic.

image.png

对于关键值的修改.通过加锁来保证并发下的安全.

image.png

timerHeaped :定时器状态位,表示该定时器已加入「定时器堆.

timerModified :标记定时器的触发时间 / 周期已修改,通知 timerproc 重新调整堆;

timerZombie :标记定时器为 “僵尸”(待从堆中移除),通常由 Stop() 触发;

t.ts :定时器所属的 timersBucket (定时器桶),Go 用分段桶减少锁竞争;

minWhenModified *:定时器桶的最小触发时间, *timerproc 会根据这个值决定休眠多久,若新 when 更小,需要唤醒 timerproc 重新计算休眠时间。

通道类型定时器的并发保护:

image.png

t.seq:序列号.用于防止过期的定时器触发事件写入通道.(比如定时器被reset之后.旧

的触发逻辑会因为seq不匹配而放弃发送).

t.isSending:用于标记是否有向通道发送数据的操作在进行.保证并发下的状态正确.

通道数据清理与锁释放:

timerchandrain :清空通道中过期的定时器事件,避免用户读取到旧数据

定时器入堆与唤醒协程:

t.mayveAdd:将定时器加入timersBucket的堆中.

wakeNetPoller:唤醒网络轮询器.

返回pending:告知上层是否有未完成触发的任务.

流程图:

image.png

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方法可以参考上面).

天亮以后.会在嘛.

*如果大家喜欢我的分享的话. *可以关注我的微信公众号

念何架构之路