1. 程式人生 > >Golang 實現簡單的定時器

Golang 實現簡單的定時器

問題

做專案的時候經常會有這樣的需求,在某個時刻開始執行某個任務,然後每隔一段時間都會執行該任務。

windows下有計劃任務,linux下有cron。如果用python可以使用apscheduler庫。那麼在Go中應該怎麼實現呢?

間隔執行

time包中有個Ticker可以用來實現簡單的定時任務。

ticker := time.NewTicker(5 * time.Second)
for _ = range ticker.C {
    fmt.Println(time.Now())
}

Ticker會在每隔一段時間執行,比如上面的例子中,每隔5秒列印一下當前時間。

但是,這顯然滿足不了我們的需求,我們還需要在某個固定時刻才開始。

最終思路

這裡提供一種比較簡單的思路。對於固定時刻T,計算T和當前時間的時間差,然後sleep到T,然後用Tikcer開始定時任務,每隔時間間隔D執行一次任務。

以什麼樣的形式提供固定時刻T以及時間間隔D

一個首要原則就是越簡單越好,最好提供一個原生的包就能解析的。

在time包中,解析時間有ParseInLocation,解析時間間隔有ParseDuration。那麼我們就可以用這兩個方法能解析的形式來表達T和D。

T的格式

time包預定義了一些格式。

const (
    ANSIC       = "Mon Jan _2 15:04:05 2006"
    UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
RubyDate = "Mon Jan 02 15:04:05 -0700 2006" RFC822 = "02 Jan 06 15:04 MST" RFC822Z = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone RFC850 = "Monday, 02-Jan-06 15:04:05 MST" RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
RFC3339 = "2006-01-02T15:04:05Z07:00" RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" Kitchen = "3:04PM" // Handy time stamps. Stamp = "Jan _2 15:04:05" StampMilli = "Jan _2 15:04:05.000" StampMicro = "Jan _2 15:04:05.000000" StampNano = "Jan _2 15:04:05.000000000" )

這裡吐槽一下,一開始不明白為什麼是2006-01-02 15:04:05這個點,一直在想那個點到底發生了什麼重大的事。後來才知道,按照美式的時間格式,也就是上面的ANSIC,月,日,時,分,秒,年,排列起來正好是123456。這麼設計是為了方便記憶。。。

當然也可以自定義時間格式,比如2006-01-02 15:04:05。一般在我們的定時任務中,我們常用的是時分秒這樣的時刻,所以T得表達方式就定義為:

15:04:05

D的格式

D的格式比較簡單,可以使用300ms, -1.5h, 2h45m這種格式。詳情見ParseDuration的函式說明

程式碼

// sched to start scheduler job at start time by interval duration.
func sched(jobFunc interface{}, start, interval string, jobArgs ...interface{}) {
    jobValue := reflect.ValueOf(jobFunc)
    if jobValue.Kind() != reflect.Func {
        log.Panic("only function can be schedule.")
    }
    if len(jobArgs) != jobValue.Type().NumIn() {
        log.Panic("The number of args valid.")
    }
    // Get job function args.
    in := make([]reflect.Value, len(jobArgs))
    for i, arg := range jobArgs {
        in[i] = reflect.ValueOf(arg)
    }

    // Get interval d.
    d, err := time.ParseDuration(interval)
    if err != nil {
        log.Panic(err)
    }
    location, err := time.LoadLocation("Asia/Shanghai")
    if err != nil {
        log.Panic(err)
    }
    t, err := time.ParseInLocation("15:04:05", start, location)
    if err != nil {
        log.Panic(err)
    }
    now := time.Now()

    // Start time.
    t = time.Date(now.Year(), now.Month(), now.Day(), t.Hour(), t.Minute(), t.Second(), 0, location)

    if now.After(t) {
        t = t.Add((now.Sub(t)/d + 1) * d)
    }

    time.Sleep(t.Sub(now))
    go jobValue.Call(in)
    ticker := time.NewTicker(d)
    go func() {
        for _ = range ticker.C {
            go jobValue.Call(in)
        }
    }()
}

結語

我只想說,感謝goroutine。