2.12 Go 定时器NewTimer、NewTicker 和time.After

1、定时器(time.NewTimer)

Go语言的定时器实质是单向通道,time.Timer结构体类型中有一个time.Time类型的单向chan,源码(src/time/time.go)如下

  1. type Timer struct {
  2. C <-chan Time
  3. r runtimeTimer
  4. }

初始化 Timer 方法为NewTimer 示例

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func main() {
  7. t := time.NewTimer(time.Second * 2)
  8. defer t.Stop()
  9. for {
  10. <-t.C
  11. fmt.Println("timer running...")
  12. // 需要重置Reset 使 t 重新开始计时
  13. t.Reset(time.Second * 2)
  14. }
  15. }

输出 timer running… timer running… timer running… timer running… 这里使用NewTimer定时器需要t.Reset重置计数时间才能接着执行。如果注释 t.Reset(time.Second * 2)会导致通道堵塞,报fatal error: all goroutines are asleep - deadlock!错误。 同时需要注意 defer t.Stop()在这里并不会停止定时器。这是因为Stop会停止Timer,停止后,Timer不会再被发送,但是Stop不会关闭通道,防止读取通道发生错误。 如果想停止定时器,只能让go程序自动结束。 示例 package main

import ( “fmt” “time” )

func main() {

  1. t := time.NewTimer(time.Second * 2)
  2. ch := make(chan bool)
  3. go func(t *time.Timer) {
  4. defer t.Stop()
  5. for {
  6. select {
  7. case <-t.C:
  8. fmt.Println("timer running....")
  9. // 需要重置Reset 使 t 重新开始计时
  10. t.Reset(time.Second * 2)
  11. case stop := <-ch:
  12. if stop {
  13. fmt.Println("timer Stop")
  14. return
  15. }
  16. }
  17. }
  18. }(t)
  19. time.Sleep(10 * time.Second)
  20. ch <- true
  21. close(ch)
  22. time.Sleep(1 * time.Second)

2、定时期(NewTicker)

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func main() {
  7. t := time.NewTicker(time.Second*2)
  8. defer t.Stop()
  9. for {
  10. <- t.C
  11. fmt.Println("Ticker running...")
  12. }
  13. }

结果 Ticker running… Ticker running… Ticker running… ticker只要定义完成后,不需要其他操作就可以定时执行。 这里的defer t.Stop()和上面示例相似,也不会停止定时器,解决办法一样。

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

3、time.After

time.After()表示多长时间长的时候后返回一条time.Time类型的通道消息。但是在取出channel内容之前不阻塞,后续程序可以继续执行。

先看源码(src/time/sleep.go)

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

通过源码我们发现它返回的是一个NewTimer(d).C,其底层是用NewTimer实现的,所以如果考虑到效率低,可以直接自己调用NewTimer。

示例1

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func main() {
  7. t := time.After(time.Second * 3)
  8. fmt.Printf("t type=%T\n", t)
  9. //阻塞3秒
  10. fmt.Println("t=", <-t)
  11. }

运行结果

t type=<-chan time.Time t= 2019-05-23 09:58:59.5103274 +0800 CST m=+3.008172101

先打印第一行,3s后打印第二行

基于time.After()特性可以配合select实现计时器

示例2

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func main() {
  7. ch1 := make(chan int, 1)
  8. ch1 <- 1
  9. for {
  10. select {
  11. case e1 := <-ch1:
  12. //如果ch1通道成功读取数据,则执行该case处理语句
  13. fmt.Printf("1th case is selected. e1=%v\n", e1)
  14. case <-time.After(time.Second*2):
  15. fmt.Println("Timed out")
  16. }
  17. }
  18. }

1th case is selected. e1=1 Timed out Timed out Timed out Timed out

select语句阻塞等待最先返回数据的channel`,如ch1通道成功读取数据,则先输出1th case is selected. e1=1,之后每隔2s输出 Timed out。

links