Go 定时器介绍

早晨,闹钟铃响,昨晚熬夜打游戏的你,决定拿起手机,设定一个 5 分钟的赖床倒计时。如果使用过番茄工作法安排工作和休息时间,那么一定也设置过工作和休息的时间间隔。帮助我们测量时间间隔的这些工具叫“定时器”,在设定好的未来时刻触发,告知使用者一个时间周期的完结和下个周期的开始。

定时器不仅仅出现在日常生活中,也是程序运行中不可或缺的一部分。举个栗子,在浏览器或者 Node.js 中使用 setInterval(callback, period, [arg1, arg2, ...]); 设置定时任务,又比如在 Java 中,调用 java.util.Timerpublic void schedule(TimerTask callback, long delay, long period)

在上述的例子中,虽然不同编程语言有不同的实现方式,但是有两个重复出现的参数: callbackperiod。光阴似箭,箭和光阴都是朝一个方向一往无前。以当前时间为起始,只需要一个 period 即可确定未来的某个时刻。每个周期需要完成的任务则由 callback 定义。

在 Go 语言中,定时器功能由 time 包提供,调用 time.Tick(period Duration) <-chan Time 可以获取到一个缓存大小为 1channel,由系统在设定的时刻,向 channel 放入一个 Time 作为信号,通知程序执行周期任务。

for {
    select {
    case <- channel:
        // 执行周期任务
        callback()
    }
}

当程序执行任务的耗时少于 period 时,一切都非常的美好。系统按时提供信号,程序快速完成任务,虽然 channel 可以缓存一个信号,但是永远用不上这个缓存位置。

程序执行任务的耗时少于 period

然鹅,当程序执行任务的耗时大于 period 时,事情就有一点点麻烦了。面对这种情况,有两种解决办法。

  1. 向程序对齐,周期时间与任务执行时间一样大,任务执行完立刻开始下一周期。
  2. 向系统对齐,周期时间固定,任务执行完等待下一周期时间点。

两种对齐方式

Go 语言采用的是第二种方式,但是 channel 缓存了一个信号,导致有细微的不一样。假设通常情况下,任务执行时间远远小于周期时间,只是有其中一个前者大于后者,执行过程如下图。2到4之间的两个周期内,执行了两次定时任务,尽管第二次任务开始时间并非设定的时间。

Go 定时器处理方案

而 Go 中定时器的信号本质上是一个时间,超过时间点任务未执行完,系统会丢弃当前信号值。就会出现上一个信号值是“03:00”,而下一个信号值是“08:00”的情况。

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.

Go 定时器信号值

评论

退出登录