robfig/cron 是Go语言实现的开源定时任务调度框架,核心代码是巧妙的使用chan + select + for实现了一个轻量
级调度协程,不但语法简洁,而且具有很好的性能。
Cron是Go中用于设置定时任务的一个库,需要注意的是,Cron库分两个大版本,v1.2和v3.0,其功能和go get地
址都是不同的,注意区分。
v1.2官方文档:https://pkg.go.dev/github.com/robfig/cron
v3官方文档:https://pkg.go.dev/github.com/robfig/cron/v3
cron github仓库:https://github.com/robfig/cron
# v1.2 go get github.com/robfig/cron # v3 go get github.com/robfig/cron/v3@v3.0.0
package main import ( "fmt" "github.com/robfig/cron/v3" "time" ) func main() { // 新建一个定时任务对象,根据cron表达式进行时间调度,cron可以精确到秒,大部分表达式格式也是从秒开始 // 默认从分开始进行时间调度 // cronTab := cron.New() // 精确到秒 cronTab := cron.New(cron.WithSeconds()) // 定义定时器调用的任务函数 task := func() { fmt.Println("hello world", time.Now()) } // 定时任务,cron表达式,每五秒一次 spec := "*/5 * * * * ?" // 添加定时任务 cronTab.AddFunc(spec, task) // 启动定时器 cronTab.Start() // 阻塞主线程停止 select {} }
# 输出信息 hello world 2023-05-30 12:48:40.0089132 +0800 CST m=+0.087419701 hello world 2023-05-30 12:48:45.0040694 +0800 CST m=+5.082575901 hello world 2023-05-30 12:48:50.001667 +0800 CST m=+10.080173501 hello world 2023-05-30 12:48:55.0013075 +0800 CST m=+15.079814001 hello world 2023-05-30 12:49:00.0011284 +0800 CST m=+20.079634901 hello world 2023-05-30 12:49:05.0080655 +0800 CST m=+25.086572001 ......
cron 表达式是一个好东西,这个东西不仅 Java 的 quartZ 能用到,Go 语言中也可以用到。
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 | * / , - ? |
特殊字符说明:
? 只能在 day 和 week 中使用,标识未说明的值,用以解决 day 和 week 的冲突,比如 * * * 10 * ? 表示每
月10号触发,而换成 * 则表示不管星期几都可触发,与前者发生冲突。
如果我们使用 crontab := cron.New(cron.WithSeconds()),我们的定时任务表达式需要为:
* * * * * *
如果我们使用 cronTab := cron.New(),我们的定时任务表达式需要为:
* * * * *,不包含秒
这 6 个 * 分别代表什么意思呢?
# 第一个*: second,范围(0 - 60) # 第二个*: 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) * * * * * *
符号 | 说明 |
---|---|
(*) | 表示 cron 表达式能匹配该字段的所有值。如在第5个字段使用星号(month),表示每个月 |
(/) | 表示增长间隔,如第1个字段(minutes) 值是 3-59/15,表示每小时的第3分钟开始执行一次,之后每隔 15 分钟执行一次(即 3、18、33、48 这些时间点执行),这里也可以表示为:3/15 |
(,) | 用于枚举值,如第6个字段值是 MON,WED,FRI,表示 星期一、三、五 执行 |
(-) | 表示一个范围,如第3个字段的值为 9-17 表示 9am 到 5pm 直接每个小时(包括9和17) |
(?) | 只用于 日(Day of month) 和 星期(Day of week),表示不指定值,可以用于代替 * |
每隔5秒执行一次:*/5 * * * * ? 每隔1分钟执行一次:0 */1 * * * ? 每天23点执行一次:0 0 23 * * ? 每天凌晨1点执行一次:0 0 1 * * ? 每月1号凌晨1点执行一次:0 0 1 1 * ? 每周一和周三晚上22:30: 00 30 22 * * 1,3 在26分、29分、33分执行一次:0 26,29,33 * * * ? 每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ? 每年三月的星期四的下午14:10和14:40: 00 10,40 14 ? 3 4
您可以使用几个预定义的表达式来代替上表的表达式,使用如下:
输入 | 描述 | 等式 |
---|---|---|
@yearly (or @annually) | 每年一次,1月1日午夜 | 0 0 0 1 1 * |
@monthly | 每月运行一次,每月第一天午夜 | 0 0 0 1 * * |
@weekly | 每周运行一次,周六/周日之间的午夜 | 0 0 0 * * 0 |
@daily (or @midnight) | 每天午夜运行一次 | 0 0 0 * * * |
@hourly | 每小时运行一次 | 0 0 * * * * |
还可以安排作业以固定的间隔执行,从添加作业或运行cron时开始。这是通过如下格式化cron规范来支持的:
@every
// 例如 c := cron.New() c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty, starting an hour thirty from now") })
默认情况下,所有时间都是基于当前时区的,也可自定义:在时间字符串前面添加一个CRON_TZ= + 具体时区
一些常用的时区:
创建cron对象时增加一个时区选项cron.WithLocation(location),location为time.LoadLocation(zone)加载的时区
对象,zone为具体的时区格式。
package main import ( "fmt" "github.com/robfig/cron/v3" "time" ) func main() { //直接配置时区 nyc, _ := time.LoadLocation("America/New_York") // cron.New(cron.WithLocation(time.UTC)) c := cron.New(cron.WithLocation(nyc),cron.WithSeconds()) c.AddFunc("*/5 * * * * ?", func() { fmt.Println("Every 5 second at New York") }) // 参数里面配置时区 c.AddFunc("CRON_TZ=Asia/Tokyo */5 * * * * ?", func() { fmt.Println("Every 5 second at Tokyo") }) c.Start() select {} }
# 输出 Every 5 second at New York Every 5 second at Tokyo Every 5 second at Tokyo Every 5 second at New York ......
自定义定时任务只需要实现Job接口的Run方法。
package main import ( "fmt" "github.com/robfig/cron/v3" "time" ) type Task1 struct { Name string } // 自定义定时任务只需要实现Job接口的Run方法 func (t *Task1) Run() { fmt.Println("Task1: ", t.Name) } type Task2 struct { Name string } // 自定义定时任务只需要实现Job接口的Run方法 func (t *Task2) Run() { fmt.Println("Task2: ", t.Name) } func main() { cronTab := cron.New(cron.WithSeconds()) // 定义定时器调用的任务函数 // 定时任务 // cron表达式,每五秒一次 spec := "*/5 * * * * ?" //定义定时器调用的任务函数 task := func() { fmt.Println("hello world", time.Now()) } // 添加多个定时器 cronTab.AddFunc(spec, task) cronTab.AddJob(spec, &Task1{Name: "tom"}) cronTab.AddJob(spec, &Task2{Name: "merry"}) // 启动定时器 cronTab.Start() // 关闭,但是不能关闭已经在执行中的任务 defer cronTab.Stop() // 阻塞主线程停止 select {} }
# 输出 Task1: tom Task2: merry hello world 2023-05-30 15:03:55.006422 +0800 CST m=+1.424751701 Task2: merry Task1: tom hello world 2023-05-30 15:04:00.0057737 +0800 CST m=+6.424103401 Task2: merry Task1: tom hello world 2023-05-30 15:04:05.0003003 +0800 CST m=+11.418630001 ......
任务抽象(业务隔离):任务抽象成一个 Job 接口,业务逻辑类只需实现该接口。
每一个实体包含一个需要运行的 Job,这是一个接口,只有一个方法:Run。
// Job is an interface for submitted cron jobs. type Job interface { Run() }
由于 Entity 中需要 Job 类型,因此,我们希望定期运行的任务,就需要实现 Job 接口。同时,由于 Job 接口只有一个无参数无返回值的方法,为了使用方便,作者提供了一个类型
// 它通过简单的实现Run()方法来实现Job接口 type FuncJob func() // 这样,任何无参数无返回值的函数,通过强制类型转换为FuncJob,就可以当作Job来使用了,AddFunc方法就是这么做的 func (f FuncJob) Run() { f() }
计划接口:通过当前时间计算任务的下次执行执行时间,具体实现类可以根据实际需求实现。
每个实体包含一个调度器(Schedule)负责调度 Job 的执行。它也是一个接口,Schedule 的具体实现通过解析 Cron
表达式得到。库中提供了 Schedule 的两个具体实现,分别是 SpecSchedule 和 ConstantDelaySchedule。
// Schedule describes a job's duty cycle. type Schedule interface { // Next returns the next activation time, later than the given time. // Next is invoked initially, and then each time the job is run. // 返回同一Entity中的Job下一次执行的时间 Next(time.Time) time.Time }
从开始介绍的 Cron 表达式可以容易得知各个字段的意思,同时,对各种表达式的解析也会最终得到一个
SpecSchedule 的实例。库中的 Parse 返回的其实就是 SpecSchedule 的实例(当然也就实现了 Schedule 接口)。
// 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 { Second, Minute, Hour, Dom, Month, Dow uint64 // Override location for this schedule. Location *time.Location }
该类的 Next 方法实现比较多,这里就不介绍了。
// ConstantDelaySchedule represents a simple recurring duty cycle, e.g. "Every 5 minutes". // It does not support jobs more frequent than once a second. type ConstantDelaySchedule struct { // 循环的时间间隔 Delay time.Duration }
这是一个简单的循环调度器,如:每 5 分钟。注意,最小单位是秒,不能比秒还小,比如毫秒。
实现:
// Next returns the next time this should be run. // This rounds so that the next activation time will be on the second. func (schedule ConstantDelaySchedule) Next(t time.Time) time.Time { return t.Add(schedule.Delay - time.Duration(t.Nanosecond())*time.Nanosecond) }
通过 Every 函数可以获取该类型的实例,如下得到的是一个每 5 秒执行一次的调度器。
package main import ( "fmt" "github.com/robfig/cron/v3" ) func main() { constDelaySchedule1 := cron.Every(5e9) // 5s fmt.Println(constDelaySchedule1.Delay) constDelaySchedule2 := cron.Every(5e6) // 1s fmt.Println(constDelaySchedule2.Delay) }
Every 的实现:
// Every returns a crontab Schedule that activates once every duration. // Delays of less than a second are not supported (will round up to 1 second). // Any fields less than a Second are truncated. func Every(duration time.Duration) ConstantDelaySchedule { if duration < time.Second { duration = time.Second } return ConstantDelaySchedule{ Delay: duration - time.Duration(duration.Nanoseconds())%time.Second, } }
定时任务对象:保存执行的任务Job、计算执行时间。
// Entry consists of a schedule and the func to execute on that schedule. type Entry struct { // ID is the cron-assigned ID of this entry, which may be used to look up a // snapshot or remove it. ID EntryID // Schedule on which this job should be run. // 负责调度当前Entity中的Job执行 Schedule Schedule // Next time the job will run, or the zero time if Cron has not been // started or this entry's schedule is unsatisfiable // Job下一次执行的时间 Next time.Time // Prev is the last time this job was run, or the zero time if never. // 上一次执行时间 Prev time.Time // WrappedJob is the thing to run when the Schedule is activated. WrappedJob Job // Job is the thing that was submitted to cron. // It is kept around so that user code that needs to get at the job later, // e.g. via Entries() can do so. // 要执行的Job Job Job }
任务调度管理:保存定时任务对象(Entry),调度任务执行,提供新增、删除接口(涉及关联资源竞争)和暂停。
注意:
// 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. type Cron struct { entries []*Entry chain Chain stop chan struct{} // 控制Cron实例暂停 add chan *Entry // 当Cron已经运行了,增加新的Entity是通过add这个channel实现的 remove chan EntryID snapshot chan chan []Entry // 获取当前所有entity的快照 running bool // 当已经运行时为true,否则为false logger Logger runningMu sync.Mutex location *time.Location parser Parser nextID EntryID jobWaiter sync.WaitGroup }
// Remove an entry from being run in the future. func (c *Cron) Remove(id EntryID) { c.runningMu.Lock() defer c.runningMu.Unlock() if c.running { c.remove <- id } else { c.removeEntry(id) } } // Schedule adds a Job to the Cron to be run on the given schedule. // The job is wrapped with the configured Chain. func (c *Cron) Schedule(schedule Schedule, cmd Job) EntryID { c.runningMu.Lock() defer c.runningMu.Unlock() c.nextID++ entry := &Entry{ ID: c.nextID, Schedule: schedule, WrappedJob: c.chain.Then(cmd), Job: cmd, } if !c.running { c.entries = append(c.entries, entry) } else { c.add <- entry } return entry.ID }
启动时会开启唯一协程执行run方法,计算任务执行时间,执行,任务管理等:
// New returns a new Cron job runner, modified by the given options. // // Available Settings // // Time Zone // Description: The time zone in which schedules are interpreted // Default: time.Local // // Parser // Description: Parser converts cron spec strings into cron.Schedules. // Default: Accepts this spec: https://en.wikipedia.org/wiki/Cron // // Chain // Description: Wrap submitted jobs to customize behavior. // Default: A chain that recovers panics and logs them to stderr. // // See "cron.With*" to modify the default behavior. // 实例化时,成员使用的基本是默认值 func New(opts ...Option) *Cron { c := &Cron{ entries: nil, chain: NewChain(), add: make(chan *Entry), stop: make(chan struct{}), snapshot: make(chan chan []Entry), remove: make(chan EntryID), running: false, runningMu: sync.Mutex{}, logger: DefaultLogger, location: time.Local, parser: standardParser, } for _, opt := range opts { opt(c) } return c } // Start the cron scheduler in its own goroutine, or no-op if already started. func (c *Cron) Start() { c.runningMu.Lock() defer c.runningMu.Unlock() if c.running { return } c.running = true go c.run() }
核心调度:计算下次执行时间 -> 排序 -> 取最早执行数据 -> timer 等待,因为只有一个协程在执行这个run的调
度,所以不存在资源竞争,不需要加锁,另外考虑到执行任务可能涉及阻塞,例如:IO操作,所以一般startJob方
法会开启协程执行。
// Run the cron scheduler, or no-op if already running. func (c *Cron) Run() { c.runningMu.Lock() if c.running { c.runningMu.Unlock() return } c.running = true c.runningMu.Unlock() c.run() } // run the scheduler.. this is private just due to the need to synchronize // access to the 'running' state variable. func (c *Cron) run() { c.logger.Info("start") // Figure out the next activation times for each entry. now := c.now() for _, entry := range c.entries { entry.Next = entry.Schedule.Next(now) c.logger.Info("schedule", "now", now, "entry", entry.ID, "next", entry.Next) } for { // Determine the next entry to run. sort.Sort(byTime(c.entries)) var timer *time.Timer 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. timer = time.NewTimer(100000 * time.Hour) } else { timer = time.NewTimer(c.entries[0].Next.Sub(now)) } for { select { case now = <-timer.C: now = now.In(c.location) c.logger.Info("wake", "now", now) // Run every entry whose next time was less than now for _, e := range c.entries { if e.Next.After(now) || e.Next.IsZero() { break } c.startJob(e.WrappedJob) e.Prev = e.Next e.Next = e.Schedule.Next(now) c.logger.Info("run", "now", now, "entry", e.ID, "next", e.Next) } case newEntry := <-c.add: timer.Stop() now = c.now() newEntry.Next = newEntry.Schedule.Next(now) c.entries = append(c.entries, newEntry) c.logger.Info("added", "now", now, "entry", newEntry.ID, "next", newEntry.Next) case replyChan := <-c.snapshot: replyChan <- c.entrySnapshot() continue case <-c.stop: timer.Stop() c.logger.Info("stop") return case id := <-c.remove: timer.Stop() now = c.now() c.removeEntry(id) c.logger.Info("removed", "entry", id) } break } } } // startJob runs the given job in a new goroutine. func (c *Cron) startJob(j Job) { c.jobWaiter.Add(1) go func() { defer c.jobWaiter.Done() j.Run() }() }
// EntryID标识Cron实例中的entry type EntryID int // 将job加入Cron中 // 如上所述,该方法只是简单的通过FuncJob类型强制转换cmd,然后调用AddJob方法 func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error) // 将job加入Cron中 // 通过Parse函数解析cron表达式spec的到调度器实例(Schedule),之后调用c.Schedule方法 func (c *Cron) AddJob(spec string, cmd Job) (EntryID, error) // 获取当前Cron总所有Entities的快照 func (c *Cron) Entries() []*Entry // Location获取时区位置 func (c *Cron) Location() *time.Location // Entry返回给定项的快照,如果找不到则返回nil func (c *Cron) Entry(id EntryID) Entry // 删除将来运行的条目 func (c *Cron) Remove(id EntryID) // 通过两个参数实例化一个Entity,然后加入当前Cron中 // 注意: 如果当前Cron未运行,则直接将该entity加入Cron中 // 否则,通过add这个成员channel将entity加入正在运行的Cron中 func (c *Cron) Schedule(schedule Schedule, cmd Job) EntryID // 新启动一个goroutine运行当前Cron func (c *Cron) Start() // 通过给stop成员发送一个struct{}{}来停止当前Cron,同时将running置为false // 从这里知道,stop只是通知Cron停止,因此往channel发一个值即可,而不关心值是多少 // 所以,成员stop定义为空struct func (c *Cron) Stop() // 运行cron调度程序,如果已经在运行,则不运行op func (c *Cron) Run()
使用协程和 Sleep方式:
package main import ( "fmt" "time" ) func main(){ go func() { for true { fmt.Println("Hello World!",time.Now()) time.Sleep(1 * time.Second) } }() select { } }
# 输出 Hello World! 2023-05-30 22:26:12.454977 +0800 CST m=+7.021813601 Hello World! 2023-05-30 22:26:13.4553086 +0800 CST m=+8.022145201 Hello World! 2023-05-30 22:26:14.4555344 +0800 CST m=+9.022371001 Hello World! 2023-05-30 22:26:15.4566625 +0800 CST m=+10.023499101 Hello World! 2023-05-30 22:26:16.4570511 +0800 CST m=+11.023887701 Hello World! 2023-05-30 22:26:17.4579146 +0800 CST m=+12.024751201 Hello World! 2023-05-30 22:26:18.4589781 +0800 CST m=+13.025814701 ......
除了使用 cron 库可以实现定时任务,使用 time 库也可以实现定时任务。
在Go语言中,可以使用 time 包提供的 Timer 和 Ticker 类型设置定时任务。Timer 用于在未来的某个时间点执行
一次任务,而 Ticker 则用于每隔一定时间执行一次任务。
Timer 实现定时器,延迟执行,这个定时器只会触发一次。
下面是一个使用 Timer 设置定时任务的例子:
package main import ( "fmt" "time" ) func main() { // 创建一个Timer实例,设置2秒后执行任务 t := time.NewTimer(2 * time.Second) // 记得释放Timer资源 defer t.Stop() // 等待Timer到期 <-t.C // 执行任务 fmt.Println("Task executed at", time.Now()) }
# 输出 Task executed at 2023-05-30 21:58:32.5390386 +0800 CST m=+2.002460501
使用 time.Stop() 停止定时器,通过向通道发送一个信号,通知定时器是否关闭。
package main import ( "fmt" "time" ) func main() { done := make(chan bool) ticker := time.NewTimer(1 * time.Second) go func() { for { select { case <-done: ticker.Stop() return case <-ticker.C: fmt.Println("Hello World!") } } }() time.Sleep(10 *time.Second) done <- true }
# 输出 Hello World!
package main import ( "fmt" "time" ) func main() { fmt.Println("hello world", time.Now()) // 创建一个定时器 // 设置7秒后执行一次 myT := time.NewTimer(7 * time.Second) // 重置定时器为1s后执行 myT.Reset(1 * time.Second) <-myT.C fmt.Println("hello world", time.Now()) }
# 输出 hello world 2023-05-30 22:13:03.4342176 +0800 CST m=+0.002177501 hello world 2023-05-30 22:13:04.4454858 +0800 CST m=+1.013445701
Ticker 也是定时器,它是一个周期性的定时器。
package main import ( "fmt" "time" ) func main() { // 创建一个Ticker实例,每隔1秒执行一次任务 ticker := time.NewTicker(1 * time.Second) // 记得释放Ticker资源 defer ticker.Stop() // 循环处理任务 for { // 等待Ticker的下一次触发 <-ticker.C // 执行任务 fmt.Println("Task executed at", time.Now()) } }
# 输出 Task executed at 2023-05-30 22:17:57.2393424 +0800 CST m=+1.007115201 Task executed at 2023-05-30 22:17:58.2428548 +0800 CST m=+2.010627601 Task executed at 2023-05-30 22:17:59.2431966 +0800 CST m=+3.010969401 Task executed at 2023-05-30 22:18:00.2455851 +0800 CST m=+4.013357901 Task executed at 2023-05-30 22:18:01.2438882 +0800 CST m=+5.011661001 ......
package main import ( "fmt" "time" ) func main() { // 创建一个定时器,每隔1秒触发一次 ticker := time.NewTicker(1 * time.Second) // 在函数退出时停止定时器 defer ticker.Stop() // timer实现定时器(延迟执行),这个定时器只会触发一次,所以想要执行定时任务需要放在for循环中 for { select { // 定时器触发时执行的任务 case <-ticker.C: fmt.Println("hello world", time.Now()) } } }
# 输出 hello world 2023-05-30 21:15:42.32353 +0800 CST m=+42.004562601 hello world 2023-05-30 21:15:43.3274285 +0800 CST m=+43.008461101 hello world 2023-05-30 21:15:44.3226011 +0800 CST m=+44.003633701 hello world 2023-05-30 21:15:45.3233505 +0800 CST m=+45.004383101 hello world 2023-05-30 21:15:46.3310151 +0800 CST m=+46.012047701 hello world 2023-05-30 21:15:47.3234301 +0800 CST m=+47.004462701 hello world 2023-05-30 21:15:48.3243248 +0800 CST m=+48.005357401 ......
package main import ( "fmt" "time" ) func main() { ticker := time.NewTicker(1 * time.Second) go func() { for range ticker.C { fmt.Println("Hello World!") } }() time.Sleep(10 * time.Second) ticker.Stop() }
# 输出 Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!
package main import ( "fmt" "time" ) func main() { done := make(chan bool) ticker := time.NewTicker(1 * time.Second) go func() { for { select { case <-done: ticker.Stop() return case <-ticker.C: fmt.Println("Hello World!") } } }() time.Sleep(10 *time.Second) done <- true }
# 输出 Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!
go get -u github.com/go-co-op/gocron
s := gocron.NewScheduler(time.UTC) s.Every(5).Seconds().Do(func(){ ... }) s.Every("5m").Do(func(){ ... }) s.Every(5).Days().Do(fu s.Every(1).Month(1, 2, 3).Do(func(){ ... }) s.Every(1).Day().At("10:30").Do(func(){ ... }) s.Every(1).Day().At("10:30;08:00").Do(func(){ ... }) s.Every(1).Day().At("10:30").At("08:00").Do(func(){ ... }) s.Every(1).MonthLastDay().Do(func(){ ... }) s.Every(2).MonthLastDay().Do(func(){ ... }) s.Cron("*/1 * * * *").Do(task) s.StartAsync() s.StartBlocking()
package main import ( "fmt" "time" "github.com/go-co-op/gocron" ) func cron1() { fmt.Println("cron1",time.Now()) } func cron2() { fmt.Println("cron2",time.Now()) } func main() { timezone, _ := time.LoadLocation("Asia/Shanghai") s := gocron.NewScheduler(timezone) // 每秒执行一次 s.Every(1).Seconds().Do(func() { go cron1() }) // 每秒执行一次 s.Every(1).Second().Do(func() { go cron2() }) s.StartBlocking() }
# 输出 cron2 2023-05-30 22:28:09.1476998 +0800 CST m=+0.002582801 cron1 2023-05-30 22:28:09.1476998 +0800 CST m=+0.002582801 cron2 2023-05-30 22:28:10.1478397 +0800 CST m=+1.002722701 cron1 2023-05-30 22:28:10.1478397 +0800 CST m=+1.002722701 cron1 2023-05-30 22:28:11.1487562 +0800 CST m=+2.003639201 cron2 2023-05-30 22:28:11.1487562 +0800 CST m=+2.003639201 cron2 2023-05-30 22:28:12.1479533 +0800 CST m=+3.002836301 cron1 2023-05-30 22:28:12.1479533 +0800 CST m=+3.002836301 ......
上一篇:MySQL以及版本介绍