Golang 定时器使用方法汇总

平时开发过程中,我们经常会遇到在将来某个时间点或者间隔一段时间重复执行函数。这个时候我们就可以考虑使用定时器,这篇文章将会与大家一起,总结下 Go 语言里 time.Timer、time.Ticker、time.After 和 time.AfterFunc 的相关用法及需要注意的地方。

Timer 定时器

00

Go 语言除了可以使用 time.Sleep() 实现延时之外,Timer 也可以实现,就像下面这样:

func main() {
    // fmt.Println("公众号:Golang来啦")
    fmt.Println("now time: ",time.Now().Format("2006-01-02 15:04:05"))
    timer := time.NewTimer(3 * time.Second)
    <-timer.C
    fmt.Println("now time: ",time.Now().Format("2006-01-02 15:04:05"))
}

输出:


now time:  2021-01-23 10:41:53
now time:  2021-01-23 10:41:56
01

我们需要深究下,实现延时的原理是什么?

从源码可以看到,timer.NewTimer() 返回的是 Timer 类型:

type Timer struct {
   C <-chan Time
   r runtimeTimer
}

传参是告诉 Timer 需要等待多长时间,Timer 是带有一个缓冲的 channal,在定时时间到达之前,没有数据写入 Timer.C,读取操作会阻塞当前协程,到达定时时间时,会向 channel 写入数据(当前时间),阻塞解除,被阻塞的协程得以恢复运行,达到延时或者定时执行的目的。

func main() {
    // fmt.Println("公众号:Golang来啦")
    fmt.Println("now time: ", time.Now().Format("2006-01-02 15:04:05"))
    timer1 := time.NewTimer(2 * time.Second)
    go func() {
        curTime := <-timer1.C           // 超时时间还没到时,协程会阻塞;超时时间到了之后会返回当前时间
        // 定时时间到了之后执行打印操作,实际开发时可以实现具体的业务逻辑
        fmt.Println("Timer 1 fired, now time: ", curTime.Format("2006-01-02 15:04:05"))
    }()


    timer2 := time.NewTimer(2 * time.Second)
    go func() {
        <-timer2.C
        fmt.Println("Timer 2 fired")
    }()
    // 取消定时器
    stop2 := timer2.Stop()
    if stop2 {
        fmt.Println("Timer 2 stop")
    }
    time.Sleep(5 * time.Second)
}

上面的代码,timer1 还没过期之前,协程会阻塞,超时时间到了之后会返回当前时间。timer2 过期时间是 2s,还没过期之前调用 time.Stop() 取消了定时器,所以 Timer 2 fired 是不会打印的。执行结果如下:

now time:  2021-01-23 14:43:15
Timer 2 stop
Timer 1 fired, now time:  2021-01-23 14:43:17
02

Timer 定时器表示在一段时间后执行,默认情况下只会执行一次;如果想再次执行的话需要调用 timer.Reset() 方法。

func main() {
    // fmt.Println("公众号:Golang来啦")
    fmt.Println("now time: ", time.Now().Format("2006-01-02 15:04:05"))
    timer1 := time.NewTimer(5 * time.Second)
    // 重新设置为 2s
    ok := timer1.Reset(2 * time.Second)
    fmt.Println("ok: ", ok)
    curTime := <-timer1.C
    fmt.Println("now time: ", curTime.Format("2006-01-02 15:04:05"))
}

上面的代码,调用 timer.Reset() 将过期时间重新设置为 2s,输出如下:

now time:  2021-01-23 14:56:27
ok:  true
now time:  2021-01-23 14:56:29

还可以设置执行次数或者无限次调用:

func main() {
    // fmt.Println("公众号:Golang来啦")
    timer1 := time.NewTimer(5 * time.Second)
    fmt.Println("开始时间: ", time.Now().Format("2006-01-02 15:04:05"))

    go func() {
        count := 0
        for {
            <-timer1.C
            fmt.Println("timer", time.Now().Format("2006-01-02 15:04:05"))

            count++
            fmt.Println("调用 Reset() 重新设置过期时间,将时间修改为 2s")
            timer1.Reset(2*time.Second)
            if count > 2 {
                fmt.Println("调用 Stop() 停止定时器")
                timer1.Stop()
            }
        }
    }()
    time.Sleep(15 * time.Second)
    fmt.Println("结束时间:", time.Now().Format("2006-01-02 15:04:05"))
}

第一次过期时间是 5s,过期之后调用 time.Reset() 函数重置成 2s,重置了 3 次,最后调用 time.Stop() 取消了定时器,输出如下:

开始时间:  2021-01-23 15:08:59
timer 2021-01-23 15:09:04
调用 Reset() 重新设置过期时间,将时间修改为 2s
timer 2021-01-23 15:09:06
调用 Reset() 重新设置过期时间,将时间修改为 2s
timer 2021-01-23 15:09:08
调用 Reset() 重新设置过期时间,将时间修改为 2s
调用 Stop() 停止定时器
结束时间: 2021-01-23 15:09:14

至于如何实现无限次定时调用,对你来说应该不难,可以结合上述代码思考下再动手实践下。

03

需要注意一点,如果调用 time.Reset() 和 time.Stop() 时,timer 已过期或者已停止,则会返回 false。

func main() {
    // fmt.Println("公众号:Golang来啦")

    // timer 过期
    timer := time.NewTimer(1 * time.Second)
    time.Sleep(2 * time.Second)
    ret := timer.Reset(2 * time.Second)
    fmt.Println(ret)

    // timer 停止
    timer = time.NewTimer(1 * time.Second)
    timer.Stop()
    ret = timer.Reset(1 * time.Second)
    fmt.Println(ret)
}

输出如下:

false
false
ok

time.After()

00

函数定义:

func After(d Duration) <-chan Time {
    return NewTimer(d).C
}

可以看到,After() 底层是基于 Timer 实现的,该函数返回 time.Time 类型的 channel,当定时时间到了之后,会将数据(当前时间)写入 channel。

调用该函数不会阻塞当前协程,但如果执行读取操作时,还未达到定时时间,会发生阻塞。

func main() {
    // fmt.Println("公众号:Golang来啦")

    fmt.Println(time.Now())
    after1 := time.After(2*time.Second)
    fmt.Println("before, time: ",time.Now())
    curTime1 := <-after1    // 读取
    fmt.Println("after , time: ",time.Now())
    fmt.Println(curTime1)

    fmt.Println()


    fmt.Println(time.Now())
    after2 := time.After(2*time.Second)
    time.Sleep(3*time.Second)         // 为了使定时时间过期
    fmt.Println("before, time: ",time.Now())
    curTime2 := <-after2   // 读取
    fmt.Println("after , time: ",time.Now())
    fmt.Println(curTime2)
}

输出:

2021-01-23 17:05:44.35444 +0800 CST m=+0.000092252
before, time:  2021-01-23 17:05:44.354657 +0800 CST m=+0.000309148
after , time:  2021-01-23 17:05:46.357352 +0800 CST m=+2.002944041
2021-01-23 17:05:46.357326 +0800 CST m=+2.002917813

2021-01-23 17:05:46.357436 +0800 CST m=+2.003027509
before, time:  2021-01-23 17:05:49.357899 +0800 CST m=+5.003400796
after , time:  2021-01-23 17:05:49.357984 +0800 CST m=+5.003486046
2021-01-23 17:05:48.358176 +0800 CST m=+4.003707692

从输出可以看出,执行读取操作时,如果定时器还未过期,读取操作将会阻塞;否则不会阻塞。

01

应用场景,我们可以使用 time.After() 实现超时控制,比如我们调用一个服务的 api,要求超时时间是 1s,可以结合 select 实现。

func main() {
    // fmt.Println("公众号:Golang来啦")

    ch := make(chan string, 1)
    go func() {
        // 假设我们在这里执行一个外部调用,2秒之后将结果写入 ch
        time.Sleep(time.Second * 2)
        ch <- "success"
    }()

    select {
    case res := <-ch:
        fmt.Println(res)
    case <-time.After(time.Second * 1):
        fmt.Println("timeout 1")
    }
}

上面的代码,我们在子协程里面模拟执行 api 调用,执行时间是 2s,超时 1s,所以 select 执行了超时的 case,打印 timeout 1;如果我们将超时时间设置成 3s,就可以正常接收 api 的返回数据。

time.AfterFunc()

00

函数定义:

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

time.AfterFunc() 用于延时执行自定义函数,会另起一个协程并等待指定时间过去,然后调用函数 f。它返回一个 Timer,可以通过调用其 Stop() 方法来取消等待和对 f 的调用。

func main() {
    // fmt.Println("公众号:Golang来啦")

    ch := make(chan int, 1)

    time.AfterFunc(10*time.Second, func() {
        fmt.Println("10 seconds over....")
        ch <- 8
    })

    for {
        select {
        case n := <-ch:
            fmt.Println(n, "is arriving")
            fmt.Println("Done!")
            return
        default:
            time.Sleep(3 * time.Second)
            fmt.Println("time to wait")
        }
    }
}

上面的代码,创建了 10s 的定时器和回调函数,执行输出:

time to wait
time to wait
time to wait
10 seconds over....
time to wait
8 is arriving
Done!

调用 Stop() 函数取消函数执行:

func main() {
    // fmt.Println("公众号:Golang来啦")

    timer1 :=time.AfterFunc(3*time.Second, func() {
        fmt.Println("10 seconds over....")
    })
    time.Sleep(1*time.Second)

    timer1.Stop()
    fmt.Println("已取消等待和函数执行")
    time.Sleep(5*time.Second)
    fmt.Println("ok")
}

输出:

已取消等待和函数执行
ok

你可能感兴趣的:(Go基础)