很多时候我们需要用到定时器,比如定期执行一段逻辑。Go语言中用到最多无外乎几种:

  1. time.Sleep()
  2. time.NewTimer()
  3. time.After()
  4. time.NewTicker()

这三种底层逻辑都是相通的。具体看网上的介绍。

Sleep和NewTimer

这两个方法类似,你可能会发现NewTimer的实现是放在sleep.go文件当中的。这两种方法都是等待指定的时间长度,只不过sleep直接阻塞执行流,NewTimer是通过发送通道信号的方式,一般用在不同协程间。他们都是一次性的,不会像NewTicker一样定时循环。

NewTimer可以通过Reset()方法再次指定时间激活。

time.After是基于NewTimer实现的,这里不介绍。

NewTicker重点分析

先看一下最常见的用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func TickerDemo() {
	stopRun := make(chan struct{}, 1)

	go func() {
		ticker := time.NewTicker(2 * time.Second)
		defer ticker.Stop()

		count := 0
		for {
			count++
			select {
			case <-stopRun:
				log.Println("Ticker demo exit.")
				return
			case <-ticker.C:
				log.Printf("Ticker counter: %d\n", count)
               	// time.Sleep(10 * time.Second)
			}
		}
	}()

	time.Sleep(25 * time.Second)
	log.Println("Ticker ready to exit.")
	time.Sleep(5 * time.Second)
	stopRun <- struct{}{}
	time.Sleep(5 * time.Second)
	log.Println("Bye.")
}

运行结果如下,注意看日志的时间:

2023/02/16 16:26:15 Ticker counter: 1
2023/02/16 16:26:17 Ticker counter: 2
2023/02/16 16:26:19 Ticker counter: 3
2023/02/16 16:26:21 Ticker counter: 4
2023/02/16 16:26:23 Ticker counter: 5
2023/02/16 16:26:25 Ticker counter: 6
2023/02/16 16:26:27 Ticker counter: 7
2023/02/16 16:26:29 Ticker counter: 8
2023/02/16 16:26:31 Ticker counter: 9
2023/02/16 16:26:33 Ticker counter: 10
2023/02/16 16:26:35 Ticker counter: 11
2023/02/16 16:26:37 Ticker counter: 12
2023/02/16 16:26:38 Ticker ready to exit.
2023/02/16 16:26:39 Ticker counter: 13
2023/02/16 16:26:41 Ticker counter: 14
2023/02/16 16:26:43 Ticker counter: 15
2023/02/16 16:26:43 Ticker demo exit.
2023/02/16 16:26:48 Bye.

修改一下,加上一行time.Sleep(10 * time.Second),就是把上面Demo中的注释去掉,结果如下:

2023/02/16 16:02:51 Ticker counter: 1
2023/02/16 16:03:01 Ticker counter: 2
2023/02/16 16:03:11 Ticker counter: 3
2023/02/16 16:03:14 Ticker ready to exit.
2023/02/16 16:03:21 Ticker demo exit.
2023/02/16 16:03:24 Bye.

再改10秒换成9秒time.Sleep(9 * time.Second),结果如下:

2023/02/16 16:30:05 Ticker counter: 1
2023/02/16 16:30:14 Ticker counter: 2
2023/02/16 16:30:23 Ticker counter: 3
2023/02/16 16:30:28 Ticker ready to exit.
2023/02/16 16:30:32 Ticker counter: 4
2023/02/16 16:30:38 Bye.

各种修改参数测试之后,这里得出几个结论:

  1. NewTicker第一次触发是在经过第一个时间间隔之后,而不是创建时。
  2. 如果ticker.C没被接收,下次时间间隔到了产生的新信号直接丢弃,而不会累计。
  3. 通道select语法其中一个case阻塞的话,其它case即使有通道数据到达也无法运行。

标准库源码分析

参考阅读:

https://www.jianshu.com/p/85205e236418

https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-timer/

标准库的定时器精度能做到MS级别,但是大量的定时任务对运行时性能影响会比较大,因为精度太高。

时间轮定时器

参考阅读:

https://xiaorui.cc/archives/6160

https://segmentfault.com/a/1190000041429846

https://zhuanlan.zhihu.com/p/515198640

时间轮方案被设计来处理大量定时任务,该方案能尽量减少循环遍历带来的性能下降。

(完)