Golang:实现 cron / 在特定时间执行任务

huangapple go评论195阅读模式
英文:

Golang: Implementing a cron / executing tasks at a specific time

问题

我一直在寻找关于如何在Go中实现在特定时间执行任务的示例,但是我找不到任何东西。

我自己实现了一个,并在答案中分享,这样其他人就可以参考自己的实现了。

英文:

I have been looking around for examples on how to implement a function that allows you to execute tasks at a certain time in Go, but I couldn't find anything.

I implemented one myself and I am sharing it in the answers, so other people can have a reference for their own implementation.

答案1

得分: 38

这是一个通用的实现,可以设置以下内容:

  • 间隔周期
  • 触发的小时
  • 触发的分钟
  • 触发的秒数

更新:(修复了内存泄漏问题)

  1. import (
  2. "fmt"
  3. "time"
  4. )
  5. const INTERVAL_PERIOD time.Duration = 24 * time.Hour
  6. const HOUR_TO_TICK int = 23
  7. const MINUTE_TO_TICK int = 00
  8. const SECOND_TO_TICK int = 03
  9. type jobTicker struct {
  10. timer *time.Timer
  11. }
  12. func runningRoutine() {
  13. jobTicker := &jobTicker{}
  14. jobTicker.updateTimer()
  15. for {
  16. <-jobTicker.timer.C
  17. fmt.Println(time.Now(), "- 刚刚触发")
  18. jobTicker.updateTimer()
  19. }
  20. }
  21. func (t *jobTicker) updateTimer() {
  22. nextTick := time.Date(time.Now().Year(), time.Now().Month(),
  23. time.Now().Day(), HOUR_TO_TICK, MINUTE_TO_TICK, SECOND_TO_TICK, 0, time.Local)
  24. if !nextTick.After(time.Now()) {
  25. nextTick = nextTick.Add(INTERVAL_PERIOD)
  26. }
  27. fmt.Println(nextTick, "- 下一次触发")
  28. diff := nextTick.Sub(time.Now())
  29. if t.timer == nil {
  30. t.timer = time.NewTimer(diff)
  31. } else {
  32. t.timer.Reset(diff)
  33. }
  34. }

以上是代码的翻译部分。

英文:

This is a general implementation, which lets you set:

  • interval period
  • hour to tick
  • minute to tick
  • second to tick

UPDATED: (the memory leak was fixed)

  1. import (
  2. &quot;fmt&quot;
  3. &quot;time&quot;
  4. )
  5. const INTERVAL_PERIOD time.Duration = 24 * time.Hour
  6. const HOUR_TO_TICK int = 23
  7. const MINUTE_TO_TICK int = 00
  8. const SECOND_TO_TICK int = 03
  9. type jobTicker struct {
  10. timer *time.Timer
  11. }
  12. func runningRoutine() {
  13. jobTicker := &amp;jobTicker{}
  14. jobTicker.updateTimer()
  15. for {
  16. &lt;-jobTicker.timer.C
  17. fmt.Println(time.Now(), &quot;- just ticked&quot;)
  18. jobTicker.updateTimer()
  19. }
  20. }
  21. func (t *jobTicker) updateTimer() {
  22. nextTick := time.Date(time.Now().Year(), time.Now().Month(),
  23. time.Now().Day(), HOUR_TO_TICK, MINUTE_TO_TICK, SECOND_TO_TICK, 0, time.Local)
  24. if !nextTick.After(time.Now()) {
  25. nextTick = nextTick.Add(INTERVAL_PERIOD)
  26. }
  27. fmt.Println(nextTick, &quot;- next tick&quot;)
  28. diff := nextTick.Sub(time.Now())
  29. if t.timer == nil {
  30. t.timer = time.NewTimer(diff)
  31. } else {
  32. t.timer.Reset(diff)
  33. }
  34. }

答案2

得分: 29

如果有人在寻找快速解决方案时进入这个问题,我找到了一个很棒的库,可以非常简单地安排任务。

链接:https://github.com/jasonlvhit/gocron

API非常简单:

  1. import (
  2. "fmt"
  3. "github.com/jasonlvhit/gocron"
  4. )
  5. func task() {
  6. fmt.Println("正在执行任务。")
  7. }
  8. func main() {
  9. s := gocron.NewScheduler()
  10. s.Every(2).Hours().Do(task)
  11. <- s.Start()
  12. }
英文:

In case someone drops in on this question searching for a quick solution.
I found a neat library that makes it really easy to schedule jobs.

Link: https://github.com/jasonlvhit/gocron

The API is pretty straightforward:

  1. import (
  2. &quot;fmt&quot;
  3. &quot;github.com/jasonlvhit/gocron&quot;
  4. )
  5. func task() {
  6. fmt.Println(&quot;Task is being performed.&quot;)
  7. }
  8. func main() {
  9. s := gocron.NewScheduler()
  10. s.Every(2).Hours().Do(task)
  11. &lt;- s.Start()
  12. }

答案3

得分: 23

@Daniele B提供的答案不够好,正如@Caleb所说,该实现会泄漏内存,因为每次创建新的ticker时,旧的ticker将永远不会被释放。

所以我对time.timer进行了封装,并在每次重置时重新设置它,这里有一个示例:

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. const INTERVAL_PERIOD time.Duration = 24 * time.Hour
  7. const HOUR_TO_TICK int = 23
  8. const MINUTE_TO_TICK int = 21
  9. const SECOND_TO_TICK int = 03
  10. type jobTicker struct {
  11. t *time.Timer
  12. }
  13. func getNextTickDuration() time.Duration {
  14. now := time.Now()
  15. nextTick := time.Date(now.Year(), now.Month(), now.Day(), HOUR_TO_TICK, MINUTE_TO_TICK, SECOND_TO_TICK, 0, time.Local)
  16. if nextTick.Before(now) {
  17. nextTick = nextTick.Add(INTERVAL_PERIOD)
  18. }
  19. return nextTick.Sub(time.Now())
  20. }
  21. func NewJobTicker() jobTicker {
  22. fmt.Println("new tick here")
  23. return jobTicker{time.NewTimer(getNextTickDuration())}
  24. }
  25. func (jt jobTicker) updateJobTicker() {
  26. fmt.Println("next tick here")
  27. jt.t.Reset(getNextTickDuration())
  28. }
  29. func main() {
  30. jt := NewJobTicker()
  31. for {
  32. <-jt.t.C
  33. fmt.Println(time.Now(), "- just ticked")
  34. jt.updateJobTicker()
  35. }
  36. }

希望对你有帮助!

英文:

the answer provided by @Daniele B is not good enough, as @Caleb says, that implementation leaks memory, because each time we create a new ticker, the old one will never be released.

so I wrap the time.timer, and reset it everytime, a example here:

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;time&quot;
  5. )
  6. const INTERVAL_PERIOD time.Duration = 24 * time.Hour
  7. const HOUR_TO_TICK int = 23
  8. const MINUTE_TO_TICK int = 21
  9. const SECOND_TO_TICK int = 03
  10. type jobTicker struct {
  11. t *time.Timer
  12. }
  13. func getNextTickDuration() time.Duration {
  14. now := time.Now()
  15. nextTick := time.Date(now.Year(), now.Month(), now.Day(), HOUR_TO_TICK, MINUTE_TO_TICK, SECOND_TO_TICK, 0, time.Local)
  16. if nextTick.Before(now) {
  17. nextTick = nextTick.Add(INTERVAL_PERIOD)
  18. }
  19. return nextTick.Sub(time.Now())
  20. }
  21. func NewJobTicker() jobTicker {
  22. fmt.Println(&quot;new tick here&quot;)
  23. return jobTicker{time.NewTimer(getNextTickDuration())}
  24. }
  25. func (jt jobTicker) updateJobTicker() {
  26. fmt.Println(&quot;next tick here&quot;)
  27. jt.t.Reset(getNextTickDuration())
  28. }
  29. func main() {
  30. jt := NewJobTicker()
  31. for {
  32. &lt;-jt.t.C
  33. fmt.Println(time.Now(), &quot;- just ticked&quot;)
  34. jt.updateJobTicker()
  35. }
  36. }

答案4

得分: 10

我已经创建了一个包,实际上支持crontab语法,如果你熟悉的话,例如:

  1. ctab := crontab.New()
  2. ctab.AddJob("*/5 * * * *", myFunc)
  3. ctab.AddJob("0 0 * * *", myFunc2)

包的链接:https://github.com/mileusna/crontab

英文:

I have created a package that actually supports crontab syntax if you are familiar with it, for example:

  1. ctab := crontab.New()
  2. ctab.AddJob(&quot;*/5 * * * *&quot;, myFunc)
  3. ctab.AddJob(&quot;0 0 * * *&quot;, myFunc2)

Package link: https://github.com/mileusna/crontab

答案5

得分: 7

这是另一种通用实现,无需第三方库。

免责声明:此实现适用于协调世界时(UTC)。如果需要管理时区,需要进行修改。

每天中午运行一次 func

  • 周期:time.Hour * 24
  • 偏移量:time.Hour * 12

每天在03:40(00:00 + 03:40)和15:40(12:00 + 03:40)运行两次 func

  • 周期:time.Hour * 12
  • 偏移量:time.Hour * 3 + time.Minute * 40

更新(2020-01-28):

更改:

  • 可以使用 context.Context 进行取消,使其可测试。
  • time.Ticker 可以避免计算下次执行的时间。
  1. package main
  2. import (
  3. "context"
  4. "time"
  5. )
  6. // Schedule 使用周期 `p` 和偏移量 `o` 调用函数 `f`。
  7. func Schedule(ctx context.Context, p time.Duration, o time.Duration, f func(time.Time)) {
  8. // 定位第一次执行
  9. first := time.Now().Truncate(p).Add(o)
  10. if first.Before(time.Now()) {
  11. first = first.Add(p)
  12. }
  13. firstC := time.After(first.Sub(time.Now()))
  14. // 从 nil 通道接收会永远阻塞
  15. t := &time.Ticker{C: nil}
  16. for {
  17. select {
  18. case v := <-firstC:
  19. // 在调用 f 之前必须启动 ticker,因为 f 可能需要一些时间来完成
  20. t = time.NewTicker(p)
  21. f(v)
  22. case v := <-t.C:
  23. f(v)
  24. case <-ctx.Done():
  25. t.Stop()
  26. return
  27. }
  28. }
  29. }

原始版本:

  1. package main
  2. import (
  3. "time"
  4. )
  5. // Repeat 使用周期 `d` 和偏移量 `o` 调用函数 `f`。
  6. func Repeat(d time.Duration, o time.Duration, f func(time.Time)) {
  7. next := time.Now().Truncate(d).Add(o)
  8. if next.Before(time.Now()) {
  9. next = next.Add(d)
  10. }
  11. t := time.NewTimer(next.Sub(time.Now()))
  12. for {
  13. v := <-t.C
  14. next = next.Add(d)
  15. t.Reset(next.Sub(time.Now()))
  16. f(v)
  17. }
  18. }
英文:

This is another general implementation without need for a third party library.

Disclaimer: This implementation works with UTC. For managing timezones it has to be modified.

Run a func once a day at noon.

  • Period: time.Hour * 24
  • Offset: time.Hour * 12

Run a func twice a day at 03:40 (00:00 + 03:40) and 15:40 (12:00 + 03:40).

  • Period: time.Hour * 12
  • Offset: time.Hour * 3 + time.Minute * 40

Updated (2020-01-28):

Changes:

  • context.Context can be used for cancellation, makes it testable.
  • time.Ticker removes the need for calculating the time of the next execution.
  1. package main
  2. import (
  3. &quot;context&quot;
  4. &quot;time&quot;
  5. )
  6. // Schedule calls function `f` with a period `p` offsetted by `o`.
  7. func Schedule(ctx context.Context, p time.Duration, o time.Duration, f func(time.Time)) {
  8. // Position the first execution
  9. first := time.Now().Truncate(p).Add(o)
  10. if first.Before(time.Now()) {
  11. first = first.Add(p)
  12. }
  13. firstC := time.After(first.Sub(time.Now()))
  14. // Receiving from a nil channel blocks forever
  15. t := &amp;time.Ticker{C: nil}
  16. for {
  17. select {
  18. case v := &lt;-firstC:
  19. // The ticker has to be started before f as it can take some time to finish
  20. t = time.NewTicker(p)
  21. f(v)
  22. case v := &lt;-t.C:
  23. f(v)
  24. case &lt;-ctx.Done():
  25. t.Stop()
  26. return
  27. }
  28. }
  29. }

Original:

  1. package main
  2. import (
  3. &quot;time&quot;
  4. )
  5. // Repeat calls function `f` with a period `d` offsetted by `o`.
  6. func Repeat(d time.Duration, o time.Duration, f func(time.Time)) {
  7. next := time.Now().Truncate(d).Add(o)
  8. if next.Before(time.Now()) {
  9. next = next.Add(d)
  10. }
  11. t := time.NewTimer(next.Sub(time.Now()))
  12. for {
  13. v := &lt;-t.C
  14. next = next.Add(d)
  15. t.Reset(next.Sub(time.Now()))
  16. f(v)
  17. }
  18. }

答案6

得分: 0

我正在使用https://github.com/ehsaniara/gointerlock。它还支持分布式系统,并且具有内置的分布式锁(Redis)。

  1. import (
  2. "context"
  3. "fmt"
  4. "github.com/ehsaniara/gointerlock"
  5. "log"
  6. "time"
  7. )
  8. var job = gointerlock.GoInterval{
  9. Interval: 2 * time.Second,
  10. Arg: myJob,
  11. }
  12. err := job.Run(ctx)
  13. if err != nil {
  14. log.Fatalf("Error: %s", err)
  15. }
  16. func myJob() {
  17. fmt.Println(time.Now(), " - called")
  18. }
英文:

I'm using https://github.com/ehsaniara/gointerlock. It's also supported in distributed systems and has a builtin distributer lock (Redis)

  1. import (
  2. &quot;context&quot;
  3. &quot;fmt&quot;
  4. &quot;github.com/ehsaniara/gointerlock&quot;
  5. &quot;log&quot;
  6. &quot;time&quot;
  7. )
  8. var job = gointerlock.GoInterval{
  9. Interval: 2 * time.Second,
  10. Arg: myJob,
  11. }
  12. err := job.Run(ctx)
  13. if err != nil {
  14. log.Fatalf(&quot;Error: %s&quot;, err)
  15. }
  16. func myJob() {
  17. fmt.Println(time.Now(), &quot; - called&quot;)
  18. }

huangapple
  • 本文由 发表于 2013年10月24日 02:07:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/19549199.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定