• 简介
  • 使用场景
    • 设定超时时间
    • 延迟执行某个方法
  • Timer对外接口
    • 创建定时器
    • 停止定时器
    • 重置定时器
  • 简单接口
    • After()
    • AfterFunc()
  • 总结

    简介

    Timer实际上是一种单一事件的定时器,即经过指定的时间后触发一个事件,这个事件通过其本身提供的channel进行通知。之所以叫单一事件,是因为Timer只执行一次就结束,这也是Timer与Ticker的最重要的区别之一。

    通过timer.NewTimer(d Duration)可以创建一个timer,参数即等待的时间,时间到来后立即触发一个事件。

    源码包src/time/sleep.go:Timer定义了Timer数据结构:

    1. type Timer struct { // Timer代表一次定时,时间到来后仅发生一个事件。
    2. C <-chan Time
    3. r runtimeTimer
    4. }

    Timer对外仅暴露一个channel,指定的时间到来时就往该channel中写入系统时间,也即一个事件。

    本节我们介绍Timer的几个使用场景,同时再介绍其对外呈现的方法。

    使用场景

    设定超时时间

    有时我们希望从一个管道中读取数据,在管道中没有数据时,我们不想让程序永远阻塞在管道中,而是设定一个超时时间,在此时间段中如果管道中还是没有数据到来,则判定为超时。

    Go源码包中有大量类似的用法,比如从一个连接中等待数据,其简单的用法如下代码所示:

    1. func WaitChannel(conn <-chan string) bool {
    2. timer := time.NewTimer(1 * time.Second)
    3. select {
    4. case <- conn:
    5. timer.Stop()
    6. return true
    7. case <- timer.C: // 超时
    8. println("WaitChannel timeout!")
    9. return false
    10. }
    11. }

    WaitChannel作用就是检测指定的管道中是否有数据到来,通过select语句轮询conn和timer.C两个管道,timer会在1s后向timer.C写入数据,如果1s内conn还没有数据,则会判断为超时。

    延迟执行某个方法

    有时我们希望某个方法在今后的某个时刻执行,如下代码所示:

    1. func DelayFunction() {
    2. timer := time.NewTimer(5 * time.Second)
    3. select {
    4. case <- timer.C:
    5. log.Println("Delayed 5s, start to do something.")
    6. }
    7. }

    DelayFunction()会一直等待timer的事件到来才会执行后面的方法(打印)。

    Timer对外接口

    创建定时器

    使用方法func NewTimer(d Duration) *Timer指定一个时间即可创建一个Timer,Timer一经创建便开始计时,不需要额外的启动命令。

    实际上,创建Timer意味着把一个计时任务交给系统守护协程,该协程管理着所有的Timer,当Timer的时间到达后向Timer的管道中发送当前的时间作为事件。详细的实现原理我们后面会单独介绍。

    停止定时器

    Timer创建后可以随时停止,停止计时器的方法是:

    1. func (t *Timer) Stop() bool

    其返回值代表定时器有没有超时:

    • true: 定时器超时前停止,后续不会再有事件发送;
    • false: 定时器超时后停止;

    实际上,停止计时器意味着通知系统守护协程移除该定时器。详细的实现原理我们后面单独介绍。

    重置定时器

    已过期的定时器或者已停止的定时器,可以通过重置动作重新激活,重置方法如下:

    1. func (t *Timer) Reset(d Duration) bool

    重置的动作实质上是先停掉定时器,再启动。其返回值也即停掉计时器的返回值。

    需要注意的是,重置定时器虽然可以用于修改还未超时的定时器,但正确的使用方式还是针对已过期的定时器或已被停止的定时器,同时其返回值也不可靠,返回值存在的价值仅仅是与前面版本兼容。

    实际上,重置定时器意味着通知系统守护协程移除该定时器,重新设定时间后,再把定时器交给守护协程。详细的实现原理我们后面单独介绍。

    简单接口

    前面介绍了Timer的标准接口,time包同时还提供了一些简单的方法,在特定的场景下可以简化代码。

    After()

    有时我们就是想等指定的时间,没有需求提前停止定时器,也没有需求复用该定时器,那么可以使用匿名的定时器。

    func After(d Duration) <-chan Time方法创建一个定时器,并返回定时器的管道,如下代码所示:

    1. func AfterDemo() {
    2. log.Println(time.Now())
    3. <- time.After(1 * time.Second)
    4. log.Println(time.Now())
    5. }

    AfterDemo()两条打印时间间隔为1s,实际还是一个定时器,但代码变得更简洁。

    AfterFunc()

    前面我们例子中讲到延迟一个方法的调用,实际上通过AfterFunc可以更简洁。AfterFunc的原型为:

    1. func AfterFunc(d Duration, f func()) *Timer

    该方法在指定时间到来后会执行函数f。例如:

    1. func AfterFuncDemo() {
    2. log.Println("AfterFuncDemo start: ", time.Now())
    3. time.AfterFunc(1 * time.Second, func() {
    4. log.Println("AfterFuncDemo end: ", time.Now())
    5. })
    6. time.Sleep(2 * time.Second) // 等待协程退出
    7. }

    AfterFuncDemo()中先打印一个时间,然后使用AfterFunc启动一个定器,并指定定时器结束时执行一个方法打印结束时间。

    与上面的例子所不同的是,time.AfterFunc()是异步执行的,所以需要在函数最后sleep等待指定的协程退出,否则可能函数结束时协程还未执行。

    总结

    本节简单介绍了Timer的常见使用场景和接口,后面的章节再介绍Ticker、以及二者的实际细节。

    Timer内容总结如下:

    • time.NewTimer(d)创建一个Timer;
    • timer.Stop()停掉当前Timer;
    • timer.Reset(d)重置当前Timer;