Golang 定时器包括:一次性定时器(**Timer**周期性定时器(**Ticker**)

编程中经常会通过timer和ticker、AfterFunc定时器NewTicker是设定每隔多长时间触发的,是连续触发,而计时器NewTimer是等待多长时间触发的,只触发一次,两者是不同的。等待时间函数AfterFunc是在After基础上加了一个回调函数,是等待时间到来后在另外一个goroutine协程里调用。

Timer

timer创建有两种方式,time.NewTimer(Duration) 和time.After(Duration)。 后者只是对前者的一个包装

timer到固定时间后会执行一次,请注意是一次,而不是多次。但是可以通过reset来实现每隔固定时间段执行。使用timer定时器,超时后需要重置,才能继续触发。

Ticker

ticker只要定义完成,从此刻开始计时,不需要任何其他的操作,每隔固定时间都会触发。它会以一个间隔(interval)往通道发送当前时间,而通道的接收者可以以固定的时间间隔从通道中读取时间。

time.Timer

首先我们看Timer的结构定义:
  1. type Timer struct {
  2. C <-chan Time
  3. r runtimeTimer
  4. }
其中有一个C的只读channel,还有一个runtimeTimer类型的结构体,再看一下这个结构的具体结构:
  1. type runtimeTimer struct {
  2. tb uintptr
  3. i int
  4. when int64
  5. period int64
  6. f func(interface{}, uintptr) // NOTE: must not be closure
  7. arg interface{}
  8. seq uintptr
  9. }
在使用定时timer的时候都是通过 NewTimer 或 AfterFunc 函数来获取。
  1. func NewTimer(d Duration) *Timer {
  2. c := make(chan Time, 1)
  3. t := &Timer{
  4. C: c,
  5. r: runtimeTimer{
  6. when: when(d), //表示达到时间段d时候调用f
  7. f: sendTime, // f表示一个函数调用,这里的sendTime表示d时间到达时向Timer.C发送当前的时间
  8. arg: c, // arg表示在调用f的时候把参数arg传递给f,c就是用来接受sendTime发送时间的
  9. },
  10. }
  11. startTimer(&t.r)
  12. return t
  13. }

定时器的具体实现逻辑,都在 runtime 中的 time.go 中,它的实现,没有采用经典 Unix 间隔定时器 setitimer 系统调用,也没有 采用 POSIX间隔式定时器(相关系统调用:timer_create、timer_settime 和 timer_delete),而是通过四叉树堆(heep)实现的(runtimeTimer 结构中的i字段,表示在堆中的索引)。通过构建一个最小堆,保证最快拿到到期了的定时器执行。定时器的执行,在专门的 goroutine 中进行的:go timerproc()。有兴趣的同学,可以阅读 runtime/time.go 的。

time.NewTimer()实例

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func main() {
  7. myTimer := time.NewTimer(time.Second * 2) // 启动定时器
  8. var i int = 0
  9. for {
  10. select {
  11. case <-myTimer.C:
  12. i++
  13. fmt.Println("count: ", i)
  14. myTimer.Reset(time.Second * 2) // 每次使用完后需要人为重置下
  15. }
  16. }
  17. // 不再使用了,结束它
  18. myTimer.Stop()
  19. }
使用timer定时器,超时后需要重置,才能继续触发。

time.AfterFunc

Go语言的AfterFunc()函数用于等待经过的时间,此后,它将在其自己的go-routine中调用已定义的函数“f”。此外,此函数在时间包下定义。在这里,您需要导入“time”包才能使用这些函数。

  1. func AfterFunc(d Duration, f func()) *Timer
例子:
  1. // Golang program to illustrate the usage of
  2. // AfterFunc() function
  3. // Including main package
  4. package main
  5. // Importing fmt and time
  6. import (
  7. "fmt"
  8. "time"
  9. )
  10. // Main function
  11. func main() {
  12. // Defining duration parameter of
  13. // AfterFunc() method
  14. DurationOfTime:= time.Duration(3) * time.Second
  15. // Defining function parameter of
  16. // AfterFunc() method
  17. f:= func() {
  18. // Printed when its called by the
  19. // AfterFunc() method in the time
  20. // stated above
  21. fmt.Println("Function called by "+
  22. "AfterFunc() after 3 seconds")
  23. }
  24. // Calling AfterFunc() method with its
  25. // parameter
  26. Timer1:= time.AfterFunc(DurationOfTime, f)
  27. // Calling stop method
  28. // w.r.to Timer1
  29. defer Timer1.Stop()
  30. // Calling sleep method
  31. time.Sleep(10 * time.Second)
  32. }

time.Ticker

官网:https://pkg.go.dev/time#NewTicker Golang time.Timer and time.Ticker
参考URL: https://www.jianshu.com/p/2b4686b8de4a ### time.NewTicker()实例 官网:https://pkg.go.dev/time#NewTicker 官方demo
  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func main() {
  7. ticker := time.NewTicker(time.Second)
  8. defer ticker.Stop()
  9. done := make(chan bool)
  10. go func() {
  11. time.Sleep(10 * time.Second)
  12. done <- true
  13. }()
  14. for {
  15. select {
  16. case <-done:
  17. fmt.Println("Done!")
  18. return
  19. case t := <-ticker.C:
  20. fmt.Println("Current time: ", t)
  21. }
  22. }
  23. }

Ticker是一个周期触发定时的计时器,它会按照一个时间间隔往channel发送系统当前时间,而channel的接收者可以以固定的时间间隔从channel中读取事件。

相关函数:

NewTicker() 返回一个新的Ticker,该Ticker包含一个通道字段,并会每隔时间段d就向该通道发送当时的时间。它会调整时间间隔或者丢弃tick信息以适应反应慢的接收者。如果d<=0会panic。关闭该Ticker可以释放相关资源。

Stop() 关闭一个Ticker。在关闭后,将不会发送更多的tick信息。Stop不会关闭通道t.C,以避免从该通道的读取不正确的成功

demo2: time.NewTicker 周期时间到了,但是之前程序没有执行完,怎么处理?

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func main() {
  7. ticker := time.NewTicker(time.Second)
  8. defer ticker.Stop()
  9. done := make(chan bool)
  10. go func() {
  11. time.Sleep(10 * time.Second)
  12. done <- true
  13. }()
  14. for {
  15. select {
  16. case <-done:
  17. fmt.Println("Done!")
  18. return
  19. case t := <-ticker.C:
  20. fmt.Println("Current time: ", t)
  21. time.Sleep(4 * time.Second)
  22. }
  23. }
  24. }

经过代码验证:time.NewTicker定时触发执行任务,当下一次执行到来而当前任务还没有执行结束时,会等待当前任务执行完毕后再执行下一次任务。

[重要]time.NewTicker()使用总结

1、ticker 创建完之后,不是马上就有一个 tick,第一个 tick 在 x 秒(你自己写的)之后。

2、Go语言的定时器实质是单向通道,time.Timer结构体类型中有一个time.Time类型的单向chan。

3、time.NewTicker定时触发执行任务,当下一次执行到来而当前任务还没有执行结束时,会等待当前任务执行完毕后再执行下一次任务。查阅go官网的文档和经过代码验证。

4、t.Stop()在这里并不会停止定时器。这是因为Stop会停止Timer,停止后,Timer不会再被发送,但是Stop不会关闭通道,防止读取通道发生错误。

如果想停止定时器,只能让go程序自动结束。

5、Ticker 跟 Timer 的不同之处,就在于 Ticker 时间达到后不需要人为调用 Reset 方法,会自动续期。