Golang定時器Timer與Ticker的使用詳解

1、概述

在 Go 裡有很多種定時器的使用方法,像常規的 Timer、Ticker 對象,以及經常會看到的 time.After(d Duration) 和 time.Sleep(d Duration) 方法。以上這些定時器的使用方法都來自Golang 原生 time 包,使用time包可以用來執行一些定時任務或者是周期性的任務。

2、定時器使用

2.1 Timer 相關

func NewTimer(d Duration) *Timer
func (t *Timer) Reset(d Duration) bool
func (t *Timer) Stop() bool
func After(d Duration) <-chan Time
func AfterFunc(d Duration, f func()) *Timer
 
//timer例子
func main() {
   timer := time.NewTimer(3 * time.Second)  //啟動定時器,生產一個Timer對象
   select {
   case <-timer.C:
      fmt.Println("3秒執行任務")
   }
   timer.Stop() // 不再使用瞭,結束它
}
 
//time.After例子
func main() {
   tChannel := time.After(3 * time.Second) // 其內部其實是生成瞭一個Timer對象
   select {
   case <-tChannel:
      fmt.Println("3秒執行任務")
   }
}
 
func main() {
  timer := time.NewTimer(3 * time.Second)
  for {
    timer.Reset(4 * time.Second) // 這樣來復用 timer 和修改執行時間
    select {
    case <-timer.C:
      fmt.Println("每隔4秒執行任務")
    }
  }
}

從上面可以看出來 Timer 允許再次被啟用,而 time.After 返回的是一個 channel,將不可復用。

而且需要註意的是 time.After 本質上是創建瞭一個新的 Timer 結構體,隻不過暴露出去的是結構體裡的 channel 字段而已。

因此如果在 for{…}裡循環使用瞭 time.After,將會不斷的創建 Timer。如下的使用方法就會帶來性能問題:

錯誤使用:

for 裡的 time.After 將會不斷的創建 Timer 對象,雖然最終會回收,但是會造成無意義的cpu資源消耗

func main() {
   for {
      select {
      case <-time.After(3 * time.Second):
         fmt.Println("每隔3秒執行一次")
      }
   }
}

正確使用:

func main() {
   timer := time.NewTimer(3 * time.Second)
   for {
      timer.Reset(3 * time.Second) // 這裡復用瞭 timer
      select {
      case <-timer.C:
         fmt.Println("每隔3秒執行一次")
      }
   }
}

2.2 Ticker 相關

這裡的 Ticker 跟 Timer 的不同之處,就在於 Ticker 時間達到後不需要人為調用 Reset 方法,會自動續期。

func NewTicker(d Duration) *Ticker
func Tick(d Duration) <-chan Time
func (t *Ticker) Stop()
func main() {
  ticker := time.NewTicker(3 * time.Second)
  for range ticker.C {
    fmt.Print("每隔3秒執行任務")
  }
  ticker.Stop()
}

錯誤使用:

func main() {
   for {
      select {
      case <-time.Tick(3 * time.Second): // 這裡會不斷生成 ticker,而且 ticker 會進行重新調度,造成泄漏
         fmt.Println("每隔3秒執行一次")
      }
   }
} 

3、定時器使用示例

3.1 Ticker定時器

package main
import (
    "fmt"
    "time"
)
func main() {
    // Ticker 包含一個通道字段C,每隔時間段 d 就向該通道發送當時系統時間。
    // 它會調整時間間隔或者丟棄 tick 信息以適應反應慢的接收者。
    // 如果d <= 0會觸發panic。關閉該 Ticker 可以釋放相關資源。
    ticker1 := time.NewTicker(5 * time.Second)
    // 一定要調用Stop(),回收資源
    defer ticker1.Stop()
    go func(t *time.Ticker) {
        for {
            // 每5秒中從chan t.C 中讀取一次
            <-t.C
            fmt.Println("Ticker:", time.Now().Format("2006-01-02 15:04:05"))
        }
    }(ticker1)
    time.Sleep(30 * time.Second)
    fmt.Println("ok")
}  

執行結果:

Ticker: 2022-01-18 13:39:30
Ticker: 2022-01-18 13:39:35
Ticker: 2022-01-18 13:39:40
Ticker: 2022-01-18 13:39:45
Ticker: 2022-01-18 13:39:50
ok
Ticker: 2022-01-18 13:39:55

可以看到每次執行的時間間隔都是一樣的,由於main線程結束導致程序結束。

3.2 Timer定時器

package main
 
import (
    "fmt"
    "time"
)
 
func main() {
 
    // NewTimer 創建一個 Timer,它會在最少過去時間段 d 後到期,向其自身的 C 字段發送當時的時間
    timer1 := time.NewTimer(5 * time.Second)
 
    fmt.Println("開始時間:", time.Now().Format("2006-01-02 15:04:05"))
    go func(t *time.Timer) {
        times := 0
        for {
            <-t.C
            fmt.Println("timer", time.Now().Format("2006-01-02 15:04:05"))
 
            // 從t.C中獲取數據,此時time.Timer定時器結束。如果想再次調用定時器,隻能通過調用 Reset() 函數來執行
            // Reset 使 t 重新開始計時,(本方法返回後再)等待時間段 d 過去後到期。
            // 如果調用時 t 還在等待中會返回真;如果 t已經到期或者被停止瞭會返回假。
            times++
            // 調用 reset 重發數據到chan C
            fmt.Println("調用 reset 重新設置一次timer定時器,並將時間修改為2秒")
            t.Reset(2 * time.Second)
            if times > 3 {
                fmt.Println("調用 stop 停止定時器")
                t.Stop()
            }
        }
    }(timer1)
 
    time.Sleep(30 * time.Second)
    fmt.Println("結束時間:", time.Now().Format("2006-01-02 15:04:05"))
    fmt.Println("ok")
}

執行結果:

開始時間: 2022-01-18 13:25:43
timer 2022-01-18 13:25:48
調用 reset 重新設置一次timer定時器,並將時間修改為2秒
timer 2022-01-18 13:25:50
調用 reset 重新設置一次timer定時器,並將時間修改為2秒
timer 2022-01-18 13:25:52
調用 reset 重新設置一次timer定時器,並將時間修改為2秒
timer 2022-01-18 13:25:54
調用 reset 重新設置一次timer定時器,並將時間修改為2秒
調用 stop 停止定時器
結束時間: 2022-01-18 13:26:13
ok

可以看到,第一次執行時間為5秒以後。然後通過調用 time.Reset() 方法再次激活定時器,定時時間為2秒,最後通過調用 time.Stop() 把前面的定時器取消掉。

4、總結

ticker定時器表示每隔一段時間就執行一次,一般可執行多次。

timer定時器表示在一段時間後執行,默認情況下隻執行一次,如果想再次執行的話,每次都需要調用 time.Reset() 方法,此時效果類似ticker定時器。同時也可以調用 Stop() 方法取消定時器

timer定時器比ticker定時器多一個 Reset() 方法,兩者都有 Stop() 方法,表示停止定時器,底層都調用瞭stopTimer() 函數。

除瞭上面的定時器外,Go 裡的 time.Sleep 也起到瞭類似一次性使用的定時功能。隻不過 time.Sleep 使用瞭系統調用。而像上面的定時器更多的是靠 Go 的調度行為來實現。

無論哪種計時器,.C 都是一個 chan Time 類型且容量為 1 的單向 Channel,當有超過 1 個數據的時候便會被阻塞,以此保證不會被觸發多次。

到此這篇關於Golang定時器Timer與Ticker的使用詳解的文章就介紹到這瞭,更多相關Golang定時器內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: