佳木斯湛栽影视文化发展公司

主頁 > 知識庫 > Go定時器cron的使用詳解

Go定時器cron的使用詳解

熱門標簽:呼叫中心市場需求 地方門戶網(wǎng)站 Linux服務器 AI電銷 網(wǎng)站排名優(yōu)化 百度競價排名 鐵路電話系統(tǒng) 服務外包

cron是什么

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

cron表達式  

cron表達式是一個好東西,這個東西不僅Java的quartZ能用到,Go語言中也可以用到。我沒有用過Linux的cron,但網(wǎng)上說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)字段的值不區(qū)分大小寫,如: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分鐘開始執(zhí)行一次,之后每隔 15 分鐘執(zhí)行一次(即 3、18、33、48 這些時間點執(zhí)行),這里也可以表示為:3/15

3)逗號(,)

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

4)連字號(-)

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

5)問號(?)

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

6)L,W,#

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

cron舉例說明

每隔5秒執(zhí)行一次:*/5 * * * * ?

每隔1分鐘執(zhí)行一次:0 */1 * * * ?

每天23點執(zhí)行一次:0 0 23 * * ?

每天凌晨1點執(zhí)行一次:0 0 1 * * ?

每月1號凌晨1點執(zhí)行一次:0 0 1 1 * ?

在26分、29分、33分執(zhí)行一次:0 26,29,33 * * * ?

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

下載安裝

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

源碼解析

文件目錄講解 

constantdelay.go   #一個最簡單的秒級別定時系統(tǒng)。與cron無關
constantdelay_test.go #測試
cron.go        #Cron系統(tǒng)。管理一系列的cron定時任務(Schedule Job)
cron_test.go     #測試
doc.go        #說明文檔
LICENSE        #授權書 
parser.go       #解析器,解析cron格式字符串城一個具體的定時器(Schedule)
parser_test.go    #測試
README.md       #README
spec.go        #單個定時器(Schedule)結構體。如何計算自己的下一次觸發(fā)時間
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保持任意數(shù)量的條目的軌道,調(diào)用相關的func時間表指定。它可以被啟動,停止和條目,可運行的同時進行檢查。
type Cron struct {
  entries []*Entry      // 任務
  stop   chan struct{}   // 叫停止的途徑
  add   chan *Entry    // 添加新任務的方式
  snapshot chan []*Entry   // 請求獲取任務快照的方式
  running bool        // 是否在運行
  ErrorLog *log.Logger    // 出錯日志(新增屬性)
  location *time.Location   // 所在地區(qū)(新增屬性)    
}
// Entry consists of a schedule and the func to execute on that schedule.
// 入口包括時間表和可在時間表上執(zhí)行的func
type Entry struct {
    // 計時器
  Schedule Schedule
  // 下次執(zhí)行時間
  Next time.Time
  // 上次執(zhí)行時間
  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
}

// 執(zhí)行定時任務
// 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)
  }
    // 無限循環(huán)
  for {
      //通過對下一個執(zhí)行時間進行排序,判斷那些任務是下一次被執(zhí)行的,防在隊列的前面.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: // 執(zhí)行當前任務
      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表達式是用來表示一系列時間的,而時間是無法逃脫自己的區(qū)間的 , 分,秒 0 - 59 , 時 0 - 23 , 天/月 0 - 31 , 天/周 0 - 6 , 月0 - 11 。 這些本質(zhì)上都是一個點集合,或者說是一個整數(shù)區(qū)間。 那么對于任意的整數(shù)區(qū)間 , 可以描述cron的如下部分規(guī)則。

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

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

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

/* 
  ------------------------------------------------------------
  第64位標記任意 , 用于 日/周 , 日 / 月 的相互干擾。
  63 - 0 為 表示區(qū)間 [63 , 0] 的 每一個點。
  ------------------------------------------------------------

  假設區(qū)間是 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 cron

import (
  "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 int

const (
  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 optionsDowOptional > 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.optionsDescriptor > 0 {
    return parseDescriptor(spec)
  }

  // Figure out how many fields we need
  max := 0
  for _, place := range places {
    if p.optionsplace > 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 optionsplace > 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 main
import (
  "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 這里面就不一一贅述了。希望大家能夠自己探索。

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

您可能感興趣的文章:
  • Golang定時器的2種實現(xiàn)方法與區(qū)別
  • golang定時器和超時的使用詳解
  • Golang 定時器(Timer 和 Ticker),這篇文章就夠了
  • Golang中定時器的陷阱詳解
  • 用golang實現(xiàn)一個定時器任務隊列實例
  • golang中定時器cpu使用率高的現(xiàn)象詳析
  • Go語言中定時器cron的基本使用教程
  • golang time包下定時器的實現(xiàn)方法
  • Go語言實現(xiàn)定時器的方法
  • Go的固定時長定時器和周期性時長定時器

標簽:黃山 衡水 湘潭 崇左 湖南 仙桃 銅川 蘭州

巨人網(wǎng)絡通訊聲明:本文標題《Go定時器cron的使用詳解》,本文關鍵詞  ;如發(fā)現(xiàn)本文內(nèi)容存在版權問題,煩請?zhí)峁┫嚓P信息告之我們,我們將及時溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡,涉及言論、版權與本站無關。
  • 相關文章
  • 收縮
    • 微信客服
    • 微信二維碼
    • 電話咨詢

    • 400-1100-266
    台中市| 称多县| 张家川| 南宁市| 龙南县| 临城县| 阳山县| 涞源县| 辉县市| 大宁县| 泰和县| 富民县| 连城县| 武夷山市| 丹凤县| 汤阴县| 甘泉县| 泰州市| 寿光市| 周口市| 鹤峰县| 五华县| 渑池县| 孟津县| 邵武市| 沁水县| 磐石市| 甘德县| 鄂州市| 塘沽区| 双流县| 河南省| 司法| 从江县| 晋州市| 灵宝市| 平凉市| 河曲县| 客服| 永靖县| 合阳县|