a亚洲精品_精品国产91乱码一区二区三区_亚洲精品在线免费观看视频_欧美日韩亚洲国产综合_久久久久久久久久久成人_在线区

首頁 > 編程 > Golang > 正文

Go定時器cron的使用詳解

2020-04-01 18:59:42
字體:
來源:轉載
供稿:網友

cron是什么

cron的意思就是:計劃任務,說白了就是定時任務。我和系統約個時間,你在幾點幾分幾秒或者每隔幾分鐘跑一個任務(job),就那么簡單。

cron表達式  

cron表達式是一個好東西,這個東西不僅Java的quartZ能用到,Go語言中也可以用到。我沒有用過Linux的cron,但網上說Linux也是可以用crontab -e 命令來配置定時任務。Go語言和Java中都是可以精確到秒的,但是Linux中不行。

cron表達式代表一個時間的集合,使用6個空格分隔的字段表示:

 

字段名 是否必須 允許的值  允許的特定字符
秒(Seconds) 0-59 * / , -
分(Minute) 0-59 * / , -
時(Hours) 0-23 * / , -
日(Day of month) 1-31 * / , - ?
月(Month) 1-12 或 JAN-DEC * / , -
星期(Day of week) 0-6 或 SUM-SAT * / , - ?

 

1.月(Month)和星期(Day of week)字段的值不區分大小寫,如:SUN、Sun 和 sun 是一樣的。

2.星期(Day of week)字段如果沒提供,相當于是 *

 # ┌───────────── min (0 - 59) # │ ┌────────────── hour (0 - 23) # │ │ ┌─────────────── day of month (1 - 31) # │ │ │ ┌──────────────── month (1 - 12) # │ │ │ │ ┌───────────────── day of week (0 - 6) (0 to 6 are Sunday to # │ │ │ │ │         Saturday, or use names; 7 is also Sunday) # │ │ │ │ │ # │ │ │ │ │ # * * * * * command to execute

cron特定字符說明

1)星號(*)

表示 cron 表達式能匹配該字段的所有值。如在第5個字段使用星號(month),表示每個月

2)斜線(/)

表示增長間隔,如第1個字段(minutes) 值是 3-59/15,表示每小時的第3分鐘開始執行一次,之后每隔 15 分鐘執行一次(即 3、18、33、48 這些時間點執行),這里也可以表示為:3/15

3)逗號(,)

用于枚舉值,如第6個字段值是 MON,WED,FRI,表示 星期一、三、五 執行

4)連字號(-)

表示一個范圍,如第3個字段的值為 9-17 表示 9am 到 5pm 直接每個小時(包括9和17)

5)問號(?)

只用于 日(Day of month) 和 星期(Day of week),表示不指定值,可以用于代替 *

6)L,W,#

Go中沒有L,W,#的用法,下文作解釋。

cron舉例說明

每隔5秒執行一次:*/5 * * * * ?

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

每天23點執行一次:0 0 23 * * ?

每天凌晨1點執行一次:0 0 1 * * ?

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

在26分、29分、33分執行一次:0 26,29,33 * * * ?

每天的0點、13點、18點、21點都執行一次:0 0 0,13,18,21 * * ?

下載安裝

控制臺輸入 go get github.com/robfig/cron 去下載定時任務的Go包,前提是你的 $GOPATH 已經配置好

源碼解析

文件目錄講解 

constantdelay.go   #一個最簡單的秒級別定時系統。與cron無關constantdelay_test.go #測試cron.go        #Cron系統。管理一系列的cron定時任務(Schedule Job)cron_test.go     #測試doc.go        #說明文檔LICENSE        #授權書 parser.go       #解析器,解析cron格式字符串城一個具體的定時器(Schedule)parser_test.go    #測試README.md       #READMEspec.go        #單個定時器(Schedule)結構體。如何計算自己的下一次觸發時間spec_test.go     #測試

cron.go

結構體:

// Cron keeps track of any number of entries, invoking the associated func as// specified by the schedule. It may be started, stopped, and the entries may// be inspected while running. // Cron保持任意數量的條目的軌道,調用相關的func時間表指定。它可以被啟動,停止和條目,可運行的同時進行檢查。type Cron struct {  entries []*Entry      // 任務  stop   chan struct{}   // 叫停止的途徑  add   chan *Entry    // 添加新任務的方式  snapshot chan []*Entry   // 請求獲取任務快照的方式  running bool        // 是否在運行  ErrorLog *log.Logger    // 出錯日志(新增屬性)  location *time.Location   // 所在地區(新增屬性)    }
// Entry consists of a schedule and the func to execute on that schedule.// 入口包括時間表和可在時間表上執行的functype Entry struct {    // 計時器  Schedule Schedule  // 下次執行時間  Next time.Time  // 上次執行時間  Prev time.Time  // 任務  Job Job}

關鍵方法:

// 開始任務// Start the cron scheduler in its own go-routine, or no-op if already started.func (c *Cron) Start() {  if c.running {    return  }  c.running = true  go c.run()}// 結束任務// Stop stops the cron scheduler if it is running; otherwise it does nothing.func (c *Cron) Stop() {  if !c.running {    return  }  c.stop <- struct{}{}  c.running = false}// 執行定時任務// Run the scheduler.. this is private just due to the need to synchronize// access to the 'running' state variable.func (c *Cron) run() {  // Figure out the next activation times for each entry.  now := time.Now().In(c.location)  for _, entry := range c.entries {    entry.Next = entry.Schedule.Next(now)  }    // 無限循環  for {      //通過對下一個執行時間進行排序,判斷那些任務是下一次被執行的,防在隊列的前面.sort是用來做排序的    sort.Sort(byTime(c.entries))    var effective time.Time    if len(c.entries) == 0 || c.entries[0].Next.IsZero() {      // If there are no entries yet, just sleep - it still handles new entries      // and stop requests.      effective = now.AddDate(10, 0, 0)    } else {      effective = c.entries[0].Next    }    timer := time.NewTimer(effective.Sub(now))    select {    case now = <-timer.C: // 執行當前任務      now = now.In(c.location)      // Run every entry whose next time was this effective time.      for _, e := range c.entries {        if e.Next != effective {          break        }        go c.runWithRecovery(e.Job)        e.Prev = e.Next        e.Next = e.Schedule.Next(now)      }      continue    case newEntry := <-c.add: // 添加新的任務      c.entries = append(c.entries, newEntry)      newEntry.Next = newEntry.Schedule.Next(time.Now().In(c.location))    case <-c.snapshot: // 獲取快照      c.snapshot <- c.entrySnapshot()    case <-c.stop:  // 停止任務      timer.Stop()      return    }    // 'now' should be updated after newEntry and snapshot cases.    now = time.Now().In(c.location)    timer.Stop()  }}

spec.go

結構體及關鍵方法:

// SpecSchedule specifies a duty cycle (to the second granularity), based on a// traditional crontab specification. It is computed initially and stored as bit sets.type SpecSchedule struct {  // 表達式中鎖表明的,秒,分,時,日,月,周,每個都是uint64  // Dom:Day of Month,Dow:Day of week  Second, Minute, Hour, Dom, Month, Dow uint64}// bounds provides a range of acceptable values (plus a map of name to value).// 定義了表達式的結構體type bounds struct {  min, max uint  names  map[string]uint}// The bounds for each field.// 這樣就能看出各個表達式的范圍var (    seconds = bounds{0, 59, nil}    minutes = bounds{0, 59, nil}    hours  = bounds{0, 23, nil}    dom   = bounds{1, 31, nil}    months = bounds{1, 12, map[string]uint{       "jan": 1,       "feb": 2,       "mar": 3,       "apr": 4,       "may": 5,       "jun": 6,       "jul": 7,       "aug": 8,       "sep": 9,       "oct": 10,       "nov": 11,       "dec": 12,    }}    dow = bounds{0, 6, map[string]uint{       "sun": 0,       "mon": 1,       "tue": 2,       "wed": 3,       "thu": 4,       "fri": 5,       "sat": 6,    }})const (    // Set the top bit if a star was included in the expression.    starBit = 1 << 63)

看了上面的東西肯定有人疑惑為什么秒分時這些都是定義了unit64,以及定義了一個常量starBit = 1 << 63這種寫法,這是邏輯運算符。表示二進制1向左移動63位。原因如下:

cron表達式是用來表示一系列時間的,而時間是無法逃脫自己的區間的 , 分,秒 0 - 59 , 時 0 - 23 , 天/月 0 - 31 , 天/周 0 - 6 , 月0 - 11 。 這些本質上都是一個點集合,或者說是一個整數區間。 那么對于任意的整數區間 , 可以描述cron的如下部分規則。

  1. * | ? 任意 , 對應區間上的所有點。 ( 額外注意 日/周 , 日 / 月 的相互干擾。)
  2. 純數字 , 對應一個具體的點。
  3. / 分割的兩個數字 a , b, 區間上符合 a + n * b 的所有點 ( n >= 0 )。
  4. - 分割的兩個數字, 對應這兩個數字決定的區間內的所有點。
  5. L | W 需要對于特定的時間特殊判斷, 無法通用的對應到區間上的點。

至此, robfig/cron為什么不支持 L | W的原因已經明了了。去除這兩條規則后, 其余的規則其實完全可以使用點的窮舉來通用表示。 考慮到最大的區間也不過是60個點,那么使用一個uint64的整數的每一位來表示一個點便很合適了。所以定義unit64不為過

下面是go中cron表達式的方法:

/*   ------------------------------------------------------------  第64位標記任意 , 用于 日/周 , 日 / 月 的相互干擾。  63 - 0 為 表示區間 [63 , 0] 的 每一個點。  ------------------------------------------------------------  假設區間是 0 - 63 , 則有如下的例子 :  比如 0/3 的表示如下 : (表示每隔兩位為1)  * / ?      +---+--------------------------------------------------------+  | 0 | 1 0 0 1 0 0 1 ~~ ~~          1 0 0 1 0 0 1 |  +---+--------------------------------------------------------+      63 ~ ~                      ~~ 0  比如 2-5 的表示如下 : (表示從右往左2-5位上都是1)  * / ?      +---+--------------------------------------------------------+  | 0 | 0 0 0 0 ~ ~   ~~      ~  0 0 0 1 1 1 1 0 0 |  +---+--------------------------------------------------------+      63 ~ ~                      ~~ 0 比如 * 的表示如下 : (表示所有位置上都為1)  * / ?      +---+--------------------------------------------------------+  | 1 | 1 1 1 1 1 ~ ~         ~  1 1 1 1 1 1 1 1 1 |  +---+--------------------------------------------------------+      63 ~ ~                      ~~ 0 */

parser.go

將字符串解析為SpecSchedule的類。

package cronimport (  "fmt"  "math"  "strconv"  "strings"  "time")// Configuration options for creating a parser. Most options specify which// fields should be included, while others enable features. If a field is not// included the parser will assume a default value. These options do not change// the order fields are parse in.type ParseOption intconst (  Second   ParseOption = 1 << iota // Seconds field, default 0  Minute               // Minutes field, default 0  Hour                // Hours field, default 0  Dom                 // Day of month field, default *  Month                // Month field, default *  Dow                 // Day of week field, default *  DowOptional             // Optional day of week field, default *  Descriptor             // Allow descriptors such as @monthly, @weekly, etc.)var places = []ParseOption{  Second,  Minute,  Hour,  Dom,  Month,  Dow,}var defaults = []string{  "0",  "0",  "0",  "*",  "*",  "*",}// A custom Parser that can be configured.type Parser struct {  options  ParseOption  optionals int}// Creates a custom Parser with custom options.//// // Standard parser without descriptors// specParser := NewParser(Minute | Hour | Dom | Month | Dow)// sched, err := specParser.Parse("0 0 15 */3 *")//// // Same as above, just excludes time fields// subsParser := NewParser(Dom | Month | Dow)// sched, err := specParser.Parse("15 */3 *")//// // Same as above, just makes Dow optional// subsParser := NewParser(Dom | Month | DowOptional)// sched, err := specParser.Parse("15 */3")//func NewParser(options ParseOption) Parser {  optionals := 0  if options&DowOptional > 0 {    options |= Dow    optionals++  }  return Parser{options, optionals}}// Parse returns a new crontab schedule representing the given spec.// It returns a descriptive error if the spec is not valid.// It accepts crontab specs and features configured by NewParser.// 將字符串解析成為SpecSchedule 。 SpecSchedule符合Schedule接口func (p Parser) Parse(spec string) (Schedule, error) {  // 直接處理特殊的特殊的字符串  if spec[0] == '@' && p.options&Descriptor > 0 {    return parseDescriptor(spec)  }  // Figure out how many fields we need  max := 0  for _, place := range places {    if p.options&place > 0 {      max++    }  }  min := max - p.optionals  // cron利用空白拆解出獨立的items。  fields := strings.Fields(spec)  // 驗證表達式取值范圍  if count := len(fields); count < min || count > max {    if min == max {      return nil, fmt.Errorf("Expected exactly %d fields, found %d: %s", min, count, spec)    }    return nil, fmt.Errorf("Expected %d to %d fields, found %d: %s", min, max, count, spec)  }  // Fill in missing fields  fields = expandFields(fields, p.options)  var err error  field := func(field string, r bounds) uint64 {    if err != nil {      return 0    }    var bits uint64    bits, err = getField(field, r)    return bits  }  var (    second   = field(fields[0], seconds)    minute   = field(fields[1], minutes)    hour    = field(fields[2], hours)    dayofmonth = field(fields[3], dom)    month   = field(fields[4], months)    dayofweek = field(fields[5], dow)  )  if err != nil {    return nil, err  }  // 返回所需要的SpecSchedule  return &SpecSchedule{    Second: second,    Minute: minute,    Hour:  hour,    Dom:  dayofmonth,    Month: month,    Dow:  dayofweek,  }, nil}func expandFields(fields []string, options ParseOption) []string {  n := 0  count := len(fields)  expFields := make([]string, len(places))  copy(expFields, defaults)  for i, place := range places {    if options&place > 0 {      expFields[i] = fields[n]      n++    }    if n == count {      break    }  }  return expFields}var standardParser = NewParser(  Minute | Hour | Dom | Month | Dow | Descriptor,)// ParseStandard returns a new crontab schedule representing the given standardSpec// (https://en.wikipedia.org/wiki/Cron). It differs from Parse requiring to always// pass 5 entries representing: minute, hour, day of month, month and day of week,// in that order. It returns a descriptive error if the spec is not valid.//// It accepts//  - Standard crontab specs, e.g. "* * * * ?"//  - Descriptors, e.g. "@midnight", "@every 1h30m"// 這里表示不僅可以使用cron表達式,也可以使用@midnight @every等方法func ParseStandard(standardSpec string) (Schedule, error) {  return standardParser.Parse(standardSpec)}var defaultParser = NewParser(  Second | Minute | Hour | Dom | Month | DowOptional | Descriptor,)// Parse returns a new crontab schedule representing the given spec.// It returns a descriptive error if the spec is not valid.//// It accepts//  - Full crontab specs, e.g. "* * * * * ?"//  - Descriptors, e.g. "@midnight", "@every 1h30m"func Parse(spec string) (Schedule, error) {  return defaultParser.Parse(spec)}// getField returns an Int with the bits set representing all of the times that// the field represents or error parsing field value. A "field" is a comma-separated// list of "ranges".func getField(field string, r bounds) (uint64, error) {  var bits uint64  ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' })  for _, expr := range ranges {    bit, err := getRange(expr, r)    if err != nil {      return bits, err    }    bits |= bit  }  return bits, nil}// getRange returns the bits indicated by the given expression://  number | number "-" number [ "/" number ]// or error parsing range.func getRange(expr string, r bounds) (uint64, error) {  var (    start, end, step uint    rangeAndStep   = strings.Split(expr, "/")    lowAndHigh    = strings.Split(rangeAndStep[0], "-")    singleDigit   = len(lowAndHigh) == 1    err       error  )  var extra uint64  if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" {    start = r.min    end = r.max    extra = starBit  } else {    start, err = parseIntOrName(lowAndHigh[0], r.names)    if err != nil {      return 0, err    }    switch len(lowAndHigh) {    case 1:      end = start    case 2:      end, err = parseIntOrName(lowAndHigh[1], r.names)      if err != nil {        return 0, err      }    default:      return 0, fmt.Errorf("Too many hyphens: %s", expr)    }  }  switch len(rangeAndStep) {  case 1:    step = 1  case 2:    step, err = mustParseInt(rangeAndStep[1])    if err != nil {      return 0, err    }    // Special handling: "N/step" means "N-max/step".    if singleDigit {      end = r.max    }  default:    return 0, fmt.Errorf("Too many slashes: %s", expr)  }  if start < r.min {    return 0, fmt.Errorf("Beginning of range (%d) below minimum (%d): %s", start, r.min, expr)  }  if end > r.max {    return 0, fmt.Errorf("End of range (%d) above maximum (%d): %s", end, r.max, expr)  }  if start > end {    return 0, fmt.Errorf("Beginning of range (%d) beyond end of range (%d): %s", start, end, expr)  }  if step == 0 {    return 0, fmt.Errorf("Step of range should be a positive number: %s", expr)  }  return getBits(start, end, step) | extra, nil}// parseIntOrName returns the (possibly-named) integer contained in expr.func parseIntOrName(expr string, names map[string]uint) (uint, error) {  if names != nil {    if namedInt, ok := names[strings.ToLower(expr)]; ok {      return namedInt, nil    }  }  return mustParseInt(expr)}// mustParseInt parses the given expression as an int or returns an error.func mustParseInt(expr string) (uint, error) {  num, err := strconv.Atoi(expr)  if err != nil {    return 0, fmt.Errorf("Failed to parse int from %s: %s", expr, err)  }  if num < 0 {    return 0, fmt.Errorf("Negative number (%d) not allowed: %s", num, expr)  }  return uint(num), nil}// getBits sets all bits in the range [min, max], modulo the given step size.func getBits(min, max, step uint) uint64 {  var bits uint64  // If step is 1, use shifts.  if step == 1 {    return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min)  }  // Else, use a simple loop.  for i := min; i <= max; i += step {    bits |= 1 << i  }  return bits}// all returns all bits within the given bounds. (plus the star bit)func all(r bounds) uint64 {  return getBits(r.min, r.max, 1) | starBit}// parseDescriptor returns a predefined schedule for the expression, or error if none matches.func parseDescriptor(descriptor string) (Schedule, error) {  switch descriptor {  case "@yearly", "@annually":    return &SpecSchedule{      Second: 1 << seconds.min,      Minute: 1 << minutes.min,      Hour:  1 << hours.min,      Dom:  1 << dom.min,      Month: 1 << months.min,      Dow:  all(dow),    }, nil  case "@monthly":    return &SpecSchedule{      Second: 1 << seconds.min,      Minute: 1 << minutes.min,      Hour:  1 << hours.min,      Dom:  1 << dom.min,      Month: all(months),      Dow:  all(dow),    }, nil  case "@weekly":    return &SpecSchedule{      Second: 1 << seconds.min,      Minute: 1 << minutes.min,      Hour:  1 << hours.min,      Dom:  all(dom),      Month: all(months),      Dow:  1 << dow.min,    }, nil  case "@daily", "@midnight":    return &SpecSchedule{      Second: 1 << seconds.min,      Minute: 1 << minutes.min,      Hour:  1 << hours.min,      Dom:  all(dom),      Month: all(months),      Dow:  all(dow),    }, nil  case "@hourly":    return &SpecSchedule{      Second: 1 << seconds.min,      Minute: 1 << minutes.min,      Hour:  all(hours),      Dom:  all(dom),      Month: all(months),      Dow:  all(dow),    }, nil  }  const every = "@every "  if strings.HasPrefix(descriptor, every) {    duration, err := time.ParseDuration(descriptor[len(every):])    if err != nil {      return nil, fmt.Errorf("Failed to parse duration %s: %s", descriptor, err)    }    return Every(duration), nil  }  return nil, fmt.Errorf("Unrecognized descriptor: %s", descriptor)}

項目中應用

package mainimport (  "github.com/robfig/cron"  "log")func main() {  i := 0  c := cron.New()  spec := "*/5 * * * * ?"  c.AddFunc(spec, func() {    i++    log.Println("cron running:", i)  })  c.AddFunc("@every 1h1m", func() {    i++    log.Println("cron running:", i)  })  c.Start()}

注: @every 用法比較特殊,這是Go里面比較特色的用法。同樣的還有 @yearly @annually @monthly @weekly @daily @midnight @hourly 這里面就不一一贅述了。希望大家能夠自己探索。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 亚洲精品影院 | 欧美一级在线观看 | 一区二区久久 | 欧美精品一区二区三区在线播放 | 欧美一二区 | 亚洲成人日本 | 国产精品久久久久久久毛片 | 一级毛片免费观看 | 亚洲不卡视频 | 手机在线成人免费视频 | 午夜视频在线播放 | 中文字幕亚洲字幕一区二区 | 欧美乱操| 女国产精品视频一区二区三区 | 狠狠的日| 一区二区三区在线 | 伊人在线视频 | 久久久久久亚洲精品 | 国产精品二区一区 | 精品无人乱码一区二区三区 | 台湾佬成人网 | 久久不卡日韩美女 | 一区二区在线视频免费观看 | 日韩成人一级片 | www.成人.com | 国产精品久久久久久久久免费 | 二区在线观看 | 三区av | 中文字幕在线观看www | 华丽的挑战在线观看 | av网站免费观看 | 精品少妇一区二区 | 亚洲一级淫片 | 日本欧美在线观看 | 欧美精品久久久久久精华液 | 日韩中文一区 | 久久av影视 | 激情网在线观看 | 国产精品久久久久久久久岛 | 久久不卡日韩美女 | 97久久精品|