• Go -- cron定时任务的用法


    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 已经配置好

    源码解析

      文件目录讲解 

    复制代码
     1 constantdelay.go      #一个最简单的秒级别定时系统。与cron无关
     2 constantdelay_test.go #测试
     3 cron.go               #Cron系统。管理一系列的cron定时任务(Schedule Job)
     4 cron_test.go          #测试
     5 doc.go                #说明文档
     6 LICENSE               #授权书 
     7 parser.go             #解析器,解析cron格式字符串城一个具体的定时器(Schedule)
     8 parser_test.go        #测试
     9 README.md             #README
    10 spec.go               #单个定时器(Schedule)结构体。如何计算自己的下一次触发时间
    11 spec_test.go          #测试
    复制代码

      cron.go

        结构体:

    复制代码
     1 // Cron keeps track of any number of entries, invoking the associated func as
     2 // specified by the schedule. It may be started, stopped, and the entries may
     3 // be inspected while running. 
     4 // Cron保持任意数量的条目的轨道,调用相关的func时间表指定。它可以被启动,停止和条目,可运行的同时进行检查。
     5 type Cron struct {
     6     entries  []*Entry        // 任务
     7     stop     chan struct{}      // 叫停止的途径
     8     add      chan *Entry        // 添加新任务的方式
     9     snapshot chan []*Entry      // 请求获取任务快照的方式
    10     running  bool               // 是否在运行
    11     ErrorLog *log.Logger        // 出错日志(新增属性)
    12     location *time.Location     // 所在地区(新增属性)       
    13 }
    复制代码
    复制代码
     1 // Entry consists of a schedule and the func to execute on that schedule.
     2 // 入口包括时间表和可在时间表上执行的func
     3 type Entry struct {
     4         // 计时器
     5     Schedule Schedule
     6     // 下次执行时间
     7     Next time.Time
     8     // 上次执行时间
     9     Prev time.Time
    10     // 任务
    11     Job Job
    12 }
    复制代码

        关键方法:

    复制代码
     1 //  开始任务
     2 // Start the cron scheduler in its own go-routine, or no-op if already started.
     3 func (c *Cron) Start() {
     4     if c.running {
     5         return
     6     }
     7     c.running = true
     8     go c.run()
     9 }
    10 // 结束任务
    11 // Stop stops the cron scheduler if it is running; otherwise it does nothing.
    12 func (c *Cron) Stop() {
    13     if !c.running {
    14         return
    15     }
    16     c.stop <- struct{}{}
    17     c.running = false
    18 }
    19 
    20 // 执行定时任务
    21 // Run the scheduler.. this is private just due to the need to synchronize
    22 // access to the 'running' state variable.
    23 func (c *Cron) run() {
    24     // Figure out the next activation times for each entry.
    25     now := time.Now().In(c.location)
    26     for _, entry := range c.entries {
    27         entry.Next = entry.Schedule.Next(now)
    28     }
    29         // 无限循环
    30     for {
    31             //通过对下一个执行时间进行排序,判断那些任务是下一次被执行的,防在队列的前面.sort是用来做排序的
    32         sort.Sort(byTime(c.entries))
    33 
    34         var effective time.Time
    35         if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
    36             // If there are no entries yet, just sleep - it still handles new entries
    37             // and stop requests.
    38             effective = now.AddDate(10, 0, 0)
    39         } else {
    40             effective = c.entries[0].Next
    41         }
    42 
    43         timer := time.NewTimer(effective.Sub(now))
    44         select {
    45         case now = <-timer.C:  // 执行当前任务
    46             now = now.In(c.location)
    47             // Run every entry whose next time was this effective time.
    48             for _, e := range c.entries {
    49                 if e.Next != effective {
    50                     break
    51                 }
    52                 go c.runWithRecovery(e.Job)
    53                 e.Prev = e.Next
    54                 e.Next = e.Schedule.Next(now)
    55             }
    56             continue
    57 
    58         case newEntry := <-c.add:  // 添加新的任务
    59             c.entries = append(c.entries, newEntry)
    60             newEntry.Next = newEntry.Schedule.Next(time.Now().In(c.location))
    61 
    62         case <-c.snapshot:  // 获取快照
    63             c.snapshot <- c.entrySnapshot()
    64 
    65         case <-c.stop:   // 停止任务
    66             timer.Stop()
    67             return
    68         }
    69 
    70         // 'now' should be updated after newEntry and snapshot cases.
    71         now = time.Now().In(c.location)
    72         timer.Stop()
    73     }
    74 }
    复制代码

          spec.go

      结构体及关键方法:

    复制代码
     1 // SpecSchedule specifies a duty cycle (to the second granularity), based on a
     2 // traditional crontab specification. It is computed initially and stored as bit sets.
     3 type SpecSchedule struct {
     4     // 表达式中锁表明的,秒,分,时,日,月,周,每个都是uint64
     5     // Dom:Day of Month,Dow:Day of week
     6     Second, Minute, Hour, Dom, Month, Dow uint64
     7 }
     8 
     9 // bounds provides a range of acceptable values (plus a map of name to value).
    10 // 定义了表达式的结构体
    11 type bounds struct {
    12     min, max uint
    13     names    map[string]uint
    14 }
    15 
    16 
    17 // The bounds for each field.
    18 // 这样就能看出各个表达式的范围
    19 var (
    20        seconds = bounds{0, 59, nil}
    21        minutes = bounds{0, 59, nil}
    22        hours   = bounds{0, 23, nil}
    23        dom     = bounds{1, 31, nil}
    24        months  = bounds{1, 12, map[string]uint{
    25               "jan": 1,
    26               "feb": 2,
    27               "mar": 3,
    28               "apr": 4,
    29               "may": 5,
    30               "jun": 6,
    31               "jul": 7,
    32               "aug": 8,
    33               "sep": 9,
    34               "oct": 10,
    35               "nov": 11,
    36               "dec": 12,
    37        }}
    38        dow = bounds{0, 6, map[string]uint{
    39               "sun": 0,
    40               "mon": 1,
    41               "tue": 2,
    42               "wed": 3,
    43               "thu": 4,
    44               "fri": 5,
    45               "sat": 6,
    46        }}
    47 )
    48 
    49 const (
    50        // Set the top bit if a star was included in the expression.
    51        starBit = 1 << 63
    52 )
    复制代码

      看了上面的东西肯定有人疑惑为什么秒分时这些都是定义了unit64,以及定义了一个常量starBit = 1 << 63这种写法,这是逻辑运算符。表示二进制1向左移动63位。原因如下:

    cron表达式是用来表示一系列时间的,而时间是无法逃脱自己的区间的 , 分,秒 0 - 59 , 时 0 - 23 , 天/月 0 - 31 , 天/周 0 - 6 , 月0 - 11 。 这些本质上都是一个点集合,或者说是一个整数区间。 那么对于任意的整数区间 , 可以描述cron的如下部分规则。

    • * | ? 任意 , 对应区间上的所有点。 ( 额外注意 日/周 , 日 / 月 的相互干扰。)
    • 纯数字 , 对应一个具体的点。
    • / 分割的两个数字 a , b, 区间上符合 a + n * b 的所有点 ( n >= 0 )。
    • - 分割的两个数字, 对应这两个数字决定的区间内的所有点。
    • 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的类。

     

      

    复制代码
      1 package cron
      2 
      3 import (
      4     "fmt"
      5     "math"
      6     "strconv"
      7     "strings"
      8     "time"
      9 )
     10 
     11 // Configuration options for creating a parser. Most options specify which
     12 // fields should be included, while others enable features. If a field is not
     13 // included the parser will assume a default value. These options do not change
     14 // the order fields are parse in.
     15 type ParseOption int
     16 
     17 const (
     18     Second      ParseOption = 1 << iota // Seconds field, default 0
     19     Minute                              // Minutes field, default 0
     20     Hour                                // Hours field, default 0
     21     Dom                                 // Day of month field, default *
     22     Month                               // Month field, default *
     23     Dow                                 // Day of week field, default *
     24     DowOptional                         // Optional day of week field, default *
     25     Descriptor                          // Allow descriptors such as @monthly, @weekly, etc.
     26 )
     27 
     28 var places = []ParseOption{
     29     Second,
     30     Minute,
     31     Hour,
     32     Dom,
     33     Month,
     34     Dow,
     35 }
     36 
     37 var defaults = []string{
     38     "0",
     39     "0",
     40     "0",
     41     "*",
     42     "*",
     43     "*",
     44 }
     45 
     46 // A custom Parser that can be configured.
     47 type Parser struct {
     48     options   ParseOption
     49     optionals int
     50 }
     51 
     52 // Creates a custom Parser with custom options.
     53 //
     54 //  // Standard parser without descriptors
     55 //  specParser := NewParser(Minute | Hour | Dom | Month | Dow)
     56 //  sched, err := specParser.Parse("0 0 15 */3 *")
     57 //
     58 //  // Same as above, just excludes time fields
     59 //  subsParser := NewParser(Dom | Month | Dow)
     60 //  sched, err := specParser.Parse("15 */3 *")
     61 //
     62 //  // Same as above, just makes Dow optional
     63 //  subsParser := NewParser(Dom | Month | DowOptional)
     64 //  sched, err := specParser.Parse("15 */3")
     65 //
     66 func NewParser(options ParseOption) Parser {
     67     optionals := 0
     68     if options&DowOptional > 0 {
     69         options |= Dow
     70         optionals++
     71     }
     72     return Parser{options, optionals}
     73 }
     74 
     75 // Parse returns a new crontab schedule representing the given spec.
     76 // It returns a descriptive error if the spec is not valid.
     77 // It accepts crontab specs and features configured by NewParser.
     78 // 将字符串解析成为SpecSchedule 。 SpecSchedule符合Schedule接口
     79 
     80 func (p Parser) Parse(spec string) (Schedule, error) {
     81   // 直接处理特殊的特殊的字符串
     82     if spec[0] == '@' && p.options&Descriptor > 0 {
     83         return parseDescriptor(spec)
     84     }
     85 
     86     // Figure out how many fields we need
     87     max := 0
     88     for _, place := range places {
     89         if p.options&place > 0 {
     90             max++
     91         }
     92     }
     93     min := max - p.optionals
     94 
     95     // cron利用空白拆解出独立的items。
     96     fields := strings.Fields(spec)
     97 
     98     // 验证表达式取值范围
     99     if count := len(fields); count < min || count > max {
    100         if min == max {
    101             return nil, fmt.Errorf("Expected exactly %d fields, found %d: %s", min, count, spec)
    102         }
    103         return nil, fmt.Errorf("Expected %d to %d fields, found %d: %s", min, max, count, spec)
    104     }
    105 
    106     // Fill in missing fields
    107     fields = expandFields(fields, p.options)
    108 
    109     var err error
    110     field := func(field string, r bounds) uint64 {
    111         if err != nil {
    112             return 0
    113         }
    114         var bits uint64
    115         bits, err = getField(field, r)
    116         return bits
    117     }
    118 
    119     var (
    120         second     = field(fields[0], seconds)
    121         minute     = field(fields[1], minutes)
    122         hour       = field(fields[2], hours)
    123         dayofmonth = field(fields[3], dom)
    124         month      = field(fields[4], months)
    125         dayofweek  = field(fields[5], dow)
    126     )
    127     if err != nil {
    128         return nil, err
    129     }
    130     // 返回所需要的SpecSchedule
    131     return &SpecSchedule{
    132         Second: second,
    133         Minute: minute,
    134         Hour:   hour,
    135         Dom:    dayofmonth,
    136         Month:  month,
    137         Dow:    dayofweek,
    138     }, nil
    139 }
    140 
    141 func expandFields(fields []string, options ParseOption) []string {
    142     n := 0
    143     count := len(fields)
    144     expFields := make([]string, len(places))
    145     copy(expFields, defaults)
    146     for i, place := range places {
    147         if options&place > 0 {
    148             expFields[i] = fields[n]
    149             n++
    150         }
    151         if n == count {
    152             break
    153         }
    154     }
    155     return expFields
    156 }
    157 
    158 var standardParser = NewParser(
    159     Minute | Hour | Dom | Month | Dow | Descriptor,
    160 )
    161 
    162 // ParseStandard returns a new crontab schedule representing the given standardSpec
    163 // (https://en.wikipedia.org/wiki/Cron). It differs from Parse requiring to always
    164 // pass 5 entries representing: minute, hour, day of month, month and day of week,
    165 // in that order. It returns a descriptive error if the spec is not valid.
    166 //
    167 // It accepts
    168 //   - Standard crontab specs, e.g. "* * * * ?"
    169 //   - Descriptors, e.g. "@midnight", "@every 1h30m"
    170 // 这里表示不仅可以使用cron表达式,也可以使用@midnight @every等方法
    171 
    172 func ParseStandard(standardSpec string) (Schedule, error) {
    173     return standardParser.Parse(standardSpec)
    174 }
    175 
    176 var defaultParser = NewParser(
    177     Second | Minute | Hour | Dom | Month | DowOptional | Descriptor,
    178 )
    179 
    180 // Parse returns a new crontab schedule representing the given spec.
    181 // It returns a descriptive error if the spec is not valid.
    182 //
    183 // It accepts
    184 //   - Full crontab specs, e.g. "* * * * * ?"
    185 //   - Descriptors, e.g. "@midnight", "@every 1h30m"
    186 func Parse(spec string) (Schedule, error) {
    187     return defaultParser.Parse(spec)
    188 }
    189 
    190 // getField returns an Int with the bits set representing all of the times that
    191 // the field represents or error parsing field value.  A "field" is a comma-separated
    192 // list of "ranges".
    193 func getField(field string, r bounds) (uint64, error) {
    194     var bits uint64
    195     ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' })
    196     for _, expr := range ranges {
    197         bit, err := getRange(expr, r)
    198         if err != nil {
    199             return bits, err
    200         }
    201         bits |= bit
    202     }
    203     return bits, nil
    204 }
    205 
    206 // getRange returns the bits indicated by the given expression:
    207 //   number | number "-" number [ "/" number ]
    208 // or error parsing range.
    209 func getRange(expr string, r bounds) (uint64, error) {
    210     var (
    211         start, end, step uint
    212         rangeAndStep     = strings.Split(expr, "/")
    213         lowAndHigh       = strings.Split(rangeAndStep[0], "-")
    214         singleDigit      = len(lowAndHigh) == 1
    215         err              error
    216     )
    217 
    218     var extra uint64
    219     if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" {
    220         start = r.min
    221         end = r.max
    222         extra = starBit
    223     } else {
    224         start, err = parseIntOrName(lowAndHigh[0], r.names)
    225         if err != nil {
    226             return 0, err
    227         }
    228         switch len(lowAndHigh) {
    229         case 1:
    230             end = start
    231         case 2:
    232             end, err = parseIntOrName(lowAndHigh[1], r.names)
    233             if err != nil {
    234                 return 0, err
    235             }
    236         default:
    237             return 0, fmt.Errorf("Too many hyphens: %s", expr)
    238         }
    239     }
    240 
    241     switch len(rangeAndStep) {
    242     case 1:
    243         step = 1
    244     case 2:
    245         step, err = mustParseInt(rangeAndStep[1])
    246         if err != nil {
    247             return 0, err
    248         }
    249 
    250         // Special handling: "N/step" means "N-max/step".
    251         if singleDigit {
    252             end = r.max
    253         }
    254     default:
    255         return 0, fmt.Errorf("Too many slashes: %s", expr)
    256     }
    257 
    258     if start < r.min {
    259         return 0, fmt.Errorf("Beginning of range (%d) below minimum (%d): %s", start, r.min, expr)
    260     }
    261     if end > r.max {
    262         return 0, fmt.Errorf("End of range (%d) above maximum (%d): %s", end, r.max, expr)
    263     }
    264     if start > end {
    265         return 0, fmt.Errorf("Beginning of range (%d) beyond end of range (%d): %s", start, end, expr)
    266     }
    267     if step == 0 {
    268         return 0, fmt.Errorf("Step of range should be a positive number: %s", expr)
    269     }
    270 
    271     return getBits(start, end, step) | extra, nil
    272 }
    273 
    274 // parseIntOrName returns the (possibly-named) integer contained in expr.
    275 func parseIntOrName(expr string, names map[string]uint) (uint, error) {
    276     if names != nil {
    277         if namedInt, ok := names[strings.ToLower(expr)]; ok {
    278             return namedInt, nil
    279         }
    280     }
    281     return mustParseInt(expr)
    282 }
    283 
    284 // mustParseInt parses the given expression as an int or returns an error.
    285 func mustParseInt(expr string) (uint, error) {
    286     num, err := strconv.Atoi(expr)
    287     if err != nil {
    288         return 0, fmt.Errorf("Failed to parse int from %s: %s", expr, err)
    289     }
    290     if num < 0 {
    291         return 0, fmt.Errorf("Negative number (%d) not allowed: %s", num, expr)
    292     }
    293 
    294     return uint(num), nil
    295 }
    296 
    297 // getBits sets all bits in the range [min, max], modulo the given step size.
    298 func getBits(min, max, step uint) uint64 {
    299     var bits uint64
    300 
    301     // If step is 1, use shifts.
    302     if step == 1 {
    303         return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min)
    304     }
    305 
    306     // Else, use a simple loop.
    307     for i := min; i <= max; i += step {
    308         bits |= 1 << i
    309     }
    310     return bits
    311 }
    312 
    313 // all returns all bits within the given bounds.  (plus the star bit)
    314 func all(r bounds) uint64 {
    315     return getBits(r.min, r.max, 1) | starBit
    316 }
    317 
    318 // parseDescriptor returns a predefined schedule for the expression, or error if none matches.
    319 func parseDescriptor(descriptor string) (Schedule, error) {
    320     switch descriptor {
    321     case "@yearly", "@annually":
    322         return &SpecSchedule{
    323             Second: 1 << seconds.min,
    324             Minute: 1 << minutes.min,
    325             Hour:   1 << hours.min,
    326             Dom:    1 << dom.min,
    327             Month:  1 << months.min,
    328             Dow:    all(dow),
    329         }, nil
    330 
    331     case "@monthly":
    332         return &SpecSchedule{
    333             Second: 1 << seconds.min,
    334             Minute: 1 << minutes.min,
    335             Hour:   1 << hours.min,
    336             Dom:    1 << dom.min,
    337             Month:  all(months),
    338             Dow:    all(dow),
    339         }, nil
    340 
    341     case "@weekly":
    342         return &SpecSchedule{
    343             Second: 1 << seconds.min,
    344             Minute: 1 << minutes.min,
    345             Hour:   1 << hours.min,
    346             Dom:    all(dom),
    347             Month:  all(months),
    348             Dow:    1 << dow.min,
    349         }, nil
    350 
    351     case "@daily", "@midnight":
    352         return &SpecSchedule{
    353             Second: 1 << seconds.min,
    354             Minute: 1 << minutes.min,
    355             Hour:   1 << hours.min,
    356             Dom:    all(dom),
    357             Month:  all(months),
    358             Dow:    all(dow),
    359         }, nil
    360 
    361     case "@hourly":
    362         return &SpecSchedule{
    363             Second: 1 << seconds.min,
    364             Minute: 1 << minutes.min,
    365             Hour:   all(hours),
    366             Dom:    all(dom),
    367             Month:  all(months),
    368             Dow:    all(dow),
    369         }, nil
    370     }
    371 
    372     const every = "@every "
    373     if strings.HasPrefix(descriptor, every) {
    374         duration, err := time.ParseDuration(descriptor[len(every):])
    375         if err != nil {
    376             return nil, fmt.Errorf("Failed to parse duration %s: %s", descriptor, err)
    377         }
    378         return Every(duration), nil
    379     }
    380 
    381     return nil, fmt.Errorf("Unrecognized descriptor: %s", descriptor)
    382 }
    复制代码

    项目中应用

       

    复制代码
    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 这里面就不一一赘述了。希望大家能够自己探索。

      

    参考网站:

    http://blog.studygolang.com/2014/02/go_crontab/

    http://blog.csdn.net/cchd0001/article/details/51076922

    https://en.wikipedia.org/wiki/Cron

    https://github.com/robfig/cron

  • 相关阅读:
    【Daily Scrum】11-18:Postmortem of sprint 1
    【Daily Scrum】11-17
    【Daily Scrum】11-14
    【Daily Scrum】11-13
    【Daily Scrum】11-12
    【Daily Scrum】12-04
    【Daily Scrum】12-03
    【Review】Postmortem of Sprint 2 and next planning
    【Daily Scrum】11-28
    【Daily Scrum】11-27
  • 原文地址:https://www.cnblogs.com/mafeng/p/8276229.html
Copyright © 2020-2023  润新知