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 個數據的時候便會被阻塞,以此保證不會被觸發多次。

參考:https://juejin.cn/post/6884914839308533774

參考:https://blog.haohtml.com/archives/19859


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM