Go 高階: 定時器的使用

Timer

Timer 是一種單一事件定時器,就是說 Timer 只執行一次就會結束。

創建:

time.NewTimer(d Duration) :創建一個 timer

源碼包 src/time/sleep.go:Timer 定義了 Timer 數據結構:

type Timer struct {
 C <-chan Time
 r runtimeTimer
}

Timer 對外僅暴露了一個 channel,當指定時間到來就會往該 channel 中寫入系統時間,即一個事件。

timer 使用

設定超時時間

比如在一個連接中等待數據,設定一個超時時間,當時間到來還是沒有數據獲取到,則爲超時。

func WaitChannel(conn <-chan string) bool {
 timer := time.NewTimer(3 * time.Second)

 select {
 case <- conn:
  timer.Stop()
  return true
 case <- timer.C: //超時
  fmt.Println("WaitChannel timeout")
  return false
 }
}

如上示例中,select 語句輪詢 conn 和 timer.C 兩個管道,timer 會在 3s 後向 timer.C 寫入數據,如果 3s 內 conn 還沒有數據,則會判斷爲超時。

延遲執行方法

有時我們希望某個方法在今後的某個時刻執行:

func DelayFunction() {
  timer := time.NewTimer(5 * time.Second)
  
  select {
  case <- time.C
    fmt.Println("Delayed 5s,...")
  }
}

DelayFunction() 會一直等待 timer 的事件到來纔會執行後面的方法 (打印)。

Timer 對外接口

創建定時器

func NewTimer(d Duration) *Timer :

停止定時器

func (t *Timer) Stop() bool

重置定時器

func (t *Timer) Reset(d Duration) bool:

簡單接口

After()

有時我們就是想等指定的時間,沒有需求提前停止定時器,也沒有需求複用該定時器,那麼可以使用匿名的定時器:

func AfterDemo(){
  log.Println(time.Now)
  <- time.After(1 * time.Second)
  log.Println(time.Now())
}

打印時間間隔爲 1s,實際還是一個定時器,但代碼變得更簡潔。

AfterFunc()

我們可以使用 AfterFunc 更加簡潔的實現延遲一個方法的調用:

func AfterFunc(d Duration, f func()) *Timer

示例:

func AfterFuncDemo() {
  log.Println("AfterFuncDemo start", time.Now())
  time.AfterFunc(1 * time.Second, func(){
    log.Println("AfterFuncDemo end", time.Now())
  })
  
  time.Sleep(2 * time.Second) //等待協程退出
}

Ticker

Ticker 是週期性定時器,即週期性的觸發一個事件。其數據結構和 Timer 完全一致:

type Timer struct {
 C <-chan Time
 r runtimeTimer
}

在創建 Ticker 時會指定一個時間,作爲事件觸發的週期。這也是 Ticker 與 Timer 的最主要的區別。

Ticker 使用

定時任務

示例,每隔 1s 記錄一次日誌:

func TickerDemo() {
  ticker := time.NewTicker(1 * time.Second)
  defer ticker.Stop()
  
  for range ticker.C {
    log.Println("ticker...")
  }
}

for range ticker.C 會持續從管道中獲取事件,收到事件後打印一行日誌,如果管道中沒有數據會阻塞等待事件,由於 ticker 會週期性的向管道中寫入事件,所以上述程序會週期性的打印日誌。

Ticker 對外接口

創建定時器

func NewTicker(d Durtion) * Ticker :

停止定時器

func (t * Ticker) Stop() :

簡單接口

如果我們需要一個定時輪詢任務,可以使用一個簡單的 Tick 函數來獲取定時器的管道,函數原型如下:

func TIck(d Durtion) <-chan Time :

錯誤示例

func WorngTicker() {
  for {
    select {
    case <- time.Tick(1 * time.Second)
      log.Println("資源泄露")
    }
  }
}

如上錯誤示例,select 每次檢測 case 語句時都會創建一個定時器,for 循環又會不斷的執行 select 語句,所以系統裏會有越來越多的定時器不斷的消耗 CPU 資源,最終 CPU 會被耗盡。

正確用法:

func demo(t interface{}) {
    for {
        select {
        case <-t.(*time.Ticker).C:
            println("1s timer")
        }
    }
}

func main() {
    t := time.NewTicker(time.Second * 1)
    go demo(t)
    select {}
}

 圖片及部分相關技術知識點來源於網絡搜索,侵權刪!

參考資料:

https://book.douban.com/subject/35144587/

https://www.cnblogs.com/codingnote/archive/2020/03/12/12469098.html

https://blog.csdn.net/x356982611/article/details/80425030

NEW////ARRIVAL

微信公衆號

gophpython

我的微信

wucs_dd

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/er2ABNeKXjI5MGFdZ9FgMw