Go 库中的定时器是怎么实现的(持续更新...)

56 阅读1分钟

GoFrame

GoFrame 的定时方案是采用 优先队列 priority queue 实现的

优先队列 priority queue 的实现依赖于 go 原生的 container/heap

GoFrame 内部实现了定时器 Timer 具体路径为 gf/os/gtime/gtimer.go

虽然还有 Cron 的实现 gcron 但也依赖于 gtimer,所以在此讨论 gtimer 的实现原理

image.png

对于 GoFrame 的定时器只用关注几个关键点

  • Timer.ticks初始值为 0, 每个 tick + 1,无限增长
    // version v2.5.6
    if currentTimerTicks = t.ticks.Add(1); currentTimerTicks >= t.queue.NextPriority() {
        t.proceed(currentTimerTicks)
    }
    
  • Entry.ticks:表示当前任务的 生命周期,每经过多少个 Entry.ticks 执行一次 job
  • Entry.nextTicks:任务下次执行的时间点,每 Entry.ticks 更新一次,也就是 job 被执行的时候才会更新
    // version v2.5.6
    entry.nextTicks.Set(currentTimerTicks + entry.ticks)
    
  • heap queue:采用 最小堆(具体见源码)
    // version v2.5.6
    func (h *priorityQueueHeap) Less(i, j int) bool {
        return h.array[i].priority < h.array[j].priority
    }
    

补充的点

GoFrame 内部也是使用了 go 的原生定时器 Timer

func (t *Timer) loop() {
    go func() {
       var (
          ....
          timerIntervalTicker = time.NewTicker(t.options.Interval)
       )
       defer timerIntervalTicker.Stop()
       for {
          ...
       }
    }()
}

robfig/cron

image.png

cron 在每个 timer 到时都会 扫描所有的 Entry,将所有到时间的任务全部执行

case now = <-timer.C:
    ...
    for _, e := range c.entries {
       ...
       c.startJob(e.WrappedJob)
       ...
    }

timer 在外部大循环时,总是设置成 第一个任务的超时时间,当没有任务时外部循环会处于长等待

if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
    timer = time.NewTimer(100000 * time.Hour)
} else {
    timer = time.NewTimer(c.entries[0].Next.Sub(now))
}

其他

持续更新 ...