GO的定時器Timer 和定時任務cron

chenchen88發表於2021-09-06

上次我們說到了GO 中 swaggo 的應用,我們們來回顧一下

  • swaggo 是什麼
  • swagger 是什麼
  • 如何使用 swaggo
  • 如何測試 swaggo

要是對GO 中 swaggo 的應用還有點興趣的話,可以檢視文章 工作中後端是如何將API提供出去的?swaggo很不錯

之後我們可以來一次 swaggo 的原理分享,細細的瞭解一下swaggo是如何生成swagger 文件的

今天我們們來看看 GO 裡面的 *定時器 Timer 和 定時任務 cron *

我們們今天還是來看看 定時器 timer 和 定時任務 cron 如何使用,關於他們的原理,我們們後續文章會詳細分享

Timer 是什麼?

是 GO 中提供一個 定時器包,主要是用 time.Timer

timer 實際上是一種單一事件的定時器

也就是說,經過指定的時間後觸發一個事件,這個事件通過其本身提供的 通道 進行通知 , 因為Timer只執行一次就結束,所以叫他單一事件

TimerTicker最重要的區別之一 就是這裡了

大致流程是這個樣子的:

Go 執行時會啟動一個單獨的 協程

該協程 執行了一個 timerproc 的函式,維護了一個 最小堆

該協程會定期被喚醒並讀取堆頂的 timer 物件,執行該 timer 物件對應的函式(就是在 timer.C 中傳送一條資料,用於觸發定時器)

執行完畢後就會從最小堆中移除該 timer 物件

我們們建立的 time.Timer ,實際上就是在這個最小堆中新增一個 timer 物件例項,那麼我們需要停止定時器,也就是使用 timer.Stop的時候,就是從這個堆裡面刪除對應的 timer 物件

本文先不細細說明實際原理,我們們先會簡單應用它,後續會詳細分享

萬事開頭難,然後中間難,最後結尾難

Timer 如何使用?

我們們簡單看看 Timer 對應的資料結構

位置在: src/time/sleep.go:Timer

Timer代表一次定時,時間到來後只發生一個事件
只發生一次,這裡尤為重要

Timer對外僅暴露一個通道,指定的時間到了,就會往該通道中寫入系統時間,時間到了就觸發一次事件,只會觸發一次,因為時間只會到一次

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

我們們分別從如下幾個場景使用一下 Timer

  • 基本使用
  • Time 延時使用
  • 停止定時器
  • 重置定時器

基本使用

我們們設定一個 1s 中的定時器,這個定時器只會觸發一次

建立一個定時器:

func New*Timer*(d Duration) Timer

指定一個時間即可建立一個TimerTimer一經建立便開始計時,不需要額外的啟動命令

func main() {
    // 建立一個 Timer
   myT := time.NewTimer(1 * time.Second)
    // 從通道中讀取資料,若讀取得到,說明時間到了
   <- myT.C
   fmt.Println(" 1 s 時間到")

   for {}
}

Time 延時使用

設定一個 1 秒的定時,再延時 2 秒

func main() {
    // 建立一個 Timer
   myT := time.NewTimer(1 * time.Second)
   <- myT.C
   fmt.Println(" 1 s 時間到 ",time.Now().Unix())

   // 延時 2 秒
   <-time.After(2 * time.Second)
   fmt.Println(" 2 s 時間到 ",time.Now().Unix())

   for {}
}

執行程式碼執行效果如下:

 1 s 時間到  1624757781
 2 s 時間到  1624757783

GO 還提供了一個函式 AfterFunc

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

也是可以做到延遲的效果,更好的是,延遲了之後,能夠執行我們填入的函式

停止定時器

Timer 建立後可以隨時停止,我們們可以使用time.Stop()停止定時器:

func (t *Timer) Stop() bool

Stop()函式返回值是 bool,要麼是 true , 要麼是 false , 代表的含義是 定時器是否超時

  • true

定時器超時前停止,後續不會再有事件傳送了

  • false

定時器是在超時後,停止的

寫一個DEMO , 設定 1 s 的定時器

若在到了1 s ,則進行列印,說明已經超時

若沒有到 1 s ,通道就已經關閉了,則未超時

func testChannelTimeout(conn chan int) bool {
   // 設定 1 秒的定時器,若在到了1 s ,則進行列印,說明已經超時
   timer := time.NewTimer(1 * time.Second)

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

func main() {

   ch := make(chan int, 1)
    // 若開啟如下語句,則可以正常關閉定時器
    // 若註釋如下語句,則關閉定時器超時
   //ch <- 1
   go testChannelTimeout(ch)

   for {}
}

上述程式碼中,是否關閉定時器超時,跟另外一個輔助通道息息相關

若開啟如下語句,則可以正常關閉定時器

若註釋如下語句,則關閉定時器超時

ch <- 1

重置定時器

開局設定一個魚的記憶,7秒的定時器

立刻將定時器重置成 1 秒的定時器

func main() {
   // 建立一個 Timer 魚的記憶
   fmt.Println(" 開始 ", time.Now().Unix())
   myT := time.NewTimer(7 * time.Second)
   // 重置定時器為 1 s
   myT.Reset(1 * time.Second)
   <-myT.C
   fmt.Println(" 1 s 時間到 ", time.Now().Unix())

   for {}
}

執行上述程式碼後,效果如下:

 開始  1624759572
 1 s 時間到  1624759573

上述Timer 都是觸發一次,生效一次,這樣並不能滿足所有場景,例如週期性定時執行的場景就不滿足了

我們們可以使用 GO 裡面的 Ticker

Ticker 是什麼?

Ticker也是定時器,不過他是一個週期性的定時器,

也就是說,他用於週期性的觸發一個事件,通過Ticker本身提供的管道將事件傳遞出去的

Ticker對外僅暴露一個通道,指定的時間到了,就往該通道中寫入系統時間,也即一個事件。此處的時間到了,只的是週期性的時間到了

Ticker 如何使用?

位置在: src/time/tick.go:Timer

type Ticker structtype Timer struct { 一模一樣

// A Ticker holds a channel that delivers ``ticks'' of a clock
// at intervals.
type Ticker struct {
   C <-chan Time // The channel on which the ticks are delivered.
   r runtimeTimer
}

關於建立定時器 和 關閉定時器 和 上述的 Timer方法類似,我們們一起列舉出來

建立Ticker 定時器(強調:這是一個週期性的定時器)

func NewTicker(d Duration) *Ticker

關閉Ticker 定時器

func (t *Ticker) Stop()

簡單應用Ticker

設定 2 秒的 週期性定時器 Ticker

ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()

// 若通道為空,則阻塞
// 若通道有資料,則讀取
// 若通道關閉,則退出
for range ticker.C {
   fmt.Println("ticker ticker ticker ...")
}

來一個通用版本的 DEMO

週期性的執行任務,我們可以靈活設定時間,和具體處理的任務

  • 封裝Ticker的呼叫
// 定義函式型別
type Fn func() error

// 定時器中的成員
type MyTicker struct {
    MyTick *time.Ticker
    Runner Fn
}

func NewMyTick(interval int, f Fn) *MyTicker {
    return &MyTicker{
        MyTick: time.NewTicker(time.Duration(interval) * time.Second),
        Runner: f,
    }
}

// 啟動定時器需要執行的任務
func (t *MyTicker) Start() {
    for {
        select {
        case <-t.MyTick.C:
            t.Runner()
        }
    }
}

func testPrint(){
    fmt.Println(" 滴答 1 次")
}

func main() {
    t := NewMyTick( 1 ,testPrint)
    t.Start()
}

執行上述程式碼,執行效果:

滴答 1 次
滴答 1 次
滴答 1 次
...

觸發一次的Timer,週期性觸發的Ticker,我們們都應用到了

cron 是什麼?

看到 cron 小夥伴們應該不會陌生吧,用過 linux 的應該對 cron 還是有點想法的

linux裡面我們們可以使用 crontab -e 來設定定時任務,GO 裡面,我們也可以是使用 cron 包來設定定時任務

不過,linux裡面 上述定時任務只支援 分鐘以上級別

我們們的 GO 可以支援到 秒級別

cron 如何使用?

使用的包:"github.com/robfig/cron"

關於 cron 的基本語法和 在linux玩的時候類似,我們們來列舉一下:

// 每隔1秒執行一次
*/1 * * * * ?

// 每隔1分鐘執行一次
0 */1 * * * ?

// 每天0點執行一次
0 0 0 * * ?

// 每月1號凌晨1點執行一次
0 0 1 1 * ?

// 在1分、2分、3分執行一次
0 1,2,3 * * * ?

// 每天的0點、1點、2點執行一次
0 0 0,1,2 * * ?

解釋一下上述的一些字元:

  • *

匹配該欄位的所有值 , 例如 */1 * * * * ? 第 2 個 * 就是代表 每一分鐘

  • /

表示增長間隔 ,例如 0 */1 * * * ? 表示,每一隔分鐘執行一次

列舉值

例如秒, 可以寫 1到59秒鐘的任意數字, 1,3,5 * * * * ?,指的是每一分鐘的 1 , 3 ,5秒 會執行任務

其中時、分、秒的可選範圍是 1-59

日 可選範圍是 1-31

月 可選範圍是 1-12

年 可選範圍是 1-12

星期 可選範圍是 0-6 表示 週日 - 週六

  • -

表示一個範圍, 例如 1-10/2 * * * * ? ,指每分鐘的 1 -10,每隔 2 秒鐘,執行任務

  • ?

用於 表示 或者 星期

來一個簡單的例子

設定 每隔 2 秒鐘 執行一次任務

func main() {
   i := 0
   c := cron.New()
   spec := "*/2 * * * * ?"
   err := c.AddFunc(spec, func() {
      i++
      fmt.Println("cron times : ", i)
   })
   if err != nil {
      fmt.Errorf("AddFunc error : %v",err)
      return 
   }
   c.Start()

   defer c.Stop()
   select {}
}

cron 用起來還是非常簡單的,感興趣的朋友,可以多多實踐一下,關於他們的原理,我們麼後續娓娓道來

總結

  • Timer 是什麼
  • Timer 如何使用
  • Ticker 是什麼
  • Ticker 如何使用
  • cron 是什麼
  • cron 如何使用

歡迎點贊,關注,收藏

朋友們,你的支援和鼓勵,是我堅持分享,提高質量的動力

好了,本次就到這裡,下一次 GO 的日誌如何玩

技術是開放的,我們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。

我是小魔童哪吒,歡迎點贊關注收藏,下次見~

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章