英文:
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
这是一个通用的实现,可以设置以下内容:
- 间隔周期
- 触发的小时
- 触发的分钟
- 触发的秒数
更新:(修复了内存泄漏问题)
import (
"fmt"
"time"
)
const INTERVAL_PERIOD time.Duration = 24 * time.Hour
const HOUR_TO_TICK int = 23
const MINUTE_TO_TICK int = 00
const SECOND_TO_TICK int = 03
type jobTicker struct {
timer *time.Timer
}
func runningRoutine() {
jobTicker := &jobTicker{}
jobTicker.updateTimer()
for {
<-jobTicker.timer.C
fmt.Println(time.Now(), "- 刚刚触发")
jobTicker.updateTimer()
}
}
func (t *jobTicker) updateTimer() {
nextTick := time.Date(time.Now().Year(), time.Now().Month(),
time.Now().Day(), HOUR_TO_TICK, MINUTE_TO_TICK, SECOND_TO_TICK, 0, time.Local)
if !nextTick.After(time.Now()) {
nextTick = nextTick.Add(INTERVAL_PERIOD)
}
fmt.Println(nextTick, "- 下一次触发")
diff := nextTick.Sub(time.Now())
if t.timer == nil {
t.timer = time.NewTimer(diff)
} else {
t.timer.Reset(diff)
}
}
以上是代码的翻译部分。
英文:
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)
import (
"fmt"
"time"
)
const INTERVAL_PERIOD time.Duration = 24 * time.Hour
const HOUR_TO_TICK int = 23
const MINUTE_TO_TICK int = 00
const SECOND_TO_TICK int = 03
type jobTicker struct {
timer *time.Timer
}
func runningRoutine() {
jobTicker := &jobTicker{}
jobTicker.updateTimer()
for {
<-jobTicker.timer.C
fmt.Println(time.Now(), "- just ticked")
jobTicker.updateTimer()
}
}
func (t *jobTicker) updateTimer() {
nextTick := time.Date(time.Now().Year(), time.Now().Month(),
time.Now().Day(), HOUR_TO_TICK, MINUTE_TO_TICK, SECOND_TO_TICK, 0, time.Local)
if !nextTick.After(time.Now()) {
nextTick = nextTick.Add(INTERVAL_PERIOD)
}
fmt.Println(nextTick, "- next tick")
diff := nextTick.Sub(time.Now())
if t.timer == nil {
t.timer = time.NewTimer(diff)
} else {
t.timer.Reset(diff)
}
}
答案2
得分: 29
如果有人在寻找快速解决方案时进入这个问题,我找到了一个很棒的库,可以非常简单地安排任务。
链接:https://github.com/jasonlvhit/gocron
API非常简单:
import (
"fmt"
"github.com/jasonlvhit/gocron"
)
func task() {
fmt.Println("正在执行任务。")
}
func main() {
s := gocron.NewScheduler()
s.Every(2).Hours().Do(task)
<- s.Start()
}
英文:
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:
import (
"fmt"
"github.com/jasonlvhit/gocron"
)
func task() {
fmt.Println("Task is being performed.")
}
func main() {
s := gocron.NewScheduler()
s.Every(2).Hours().Do(task)
<- s.Start()
}
答案3
得分: 23
@Daniele B提供的答案不够好,正如@Caleb所说,该实现会泄漏内存,因为每次创建新的ticker时,旧的ticker将永远不会被释放。
所以我对time.timer
进行了封装,并在每次重置时重新设置它,这里有一个示例:
package main
import (
"fmt"
"time"
)
const INTERVAL_PERIOD time.Duration = 24 * time.Hour
const HOUR_TO_TICK int = 23
const MINUTE_TO_TICK int = 21
const SECOND_TO_TICK int = 03
type jobTicker struct {
t *time.Timer
}
func getNextTickDuration() time.Duration {
now := time.Now()
nextTick := time.Date(now.Year(), now.Month(), now.Day(), HOUR_TO_TICK, MINUTE_TO_TICK, SECOND_TO_TICK, 0, time.Local)
if nextTick.Before(now) {
nextTick = nextTick.Add(INTERVAL_PERIOD)
}
return nextTick.Sub(time.Now())
}
func NewJobTicker() jobTicker {
fmt.Println("new tick here")
return jobTicker{time.NewTimer(getNextTickDuration())}
}
func (jt jobTicker) updateJobTicker() {
fmt.Println("next tick here")
jt.t.Reset(getNextTickDuration())
}
func main() {
jt := NewJobTicker()
for {
<-jt.t.C
fmt.Println(time.Now(), "- just ticked")
jt.updateJobTicker()
}
}
希望对你有帮助!
英文:
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:
package main
import (
"fmt"
"time"
)
const INTERVAL_PERIOD time.Duration = 24 * time.Hour
const HOUR_TO_TICK int = 23
const MINUTE_TO_TICK int = 21
const SECOND_TO_TICK int = 03
type jobTicker struct {
t *time.Timer
}
func getNextTickDuration() time.Duration {
now := time.Now()
nextTick := time.Date(now.Year(), now.Month(), now.Day(), HOUR_TO_TICK, MINUTE_TO_TICK, SECOND_TO_TICK, 0, time.Local)
if nextTick.Before(now) {
nextTick = nextTick.Add(INTERVAL_PERIOD)
}
return nextTick.Sub(time.Now())
}
func NewJobTicker() jobTicker {
fmt.Println("new tick here")
return jobTicker{time.NewTimer(getNextTickDuration())}
}
func (jt jobTicker) updateJobTicker() {
fmt.Println("next tick here")
jt.t.Reset(getNextTickDuration())
}
func main() {
jt := NewJobTicker()
for {
<-jt.t.C
fmt.Println(time.Now(), "- just ticked")
jt.updateJobTicker()
}
}
答案4
得分: 10
我已经创建了一个包,实际上支持crontab语法,如果你熟悉的话,例如:
ctab := crontab.New()
ctab.AddJob("*/5 * * * *", myFunc)
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:
ctab := crontab.New()
ctab.AddJob("*/5 * * * *", myFunc)
ctab.AddJob("0 0 * * *", 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
可以避免计算下次执行的时间。
package main
import (
"context"
"time"
)
// Schedule 使用周期 `p` 和偏移量 `o` 调用函数 `f`。
func Schedule(ctx context.Context, p time.Duration, o time.Duration, f func(time.Time)) {
// 定位第一次执行
first := time.Now().Truncate(p).Add(o)
if first.Before(time.Now()) {
first = first.Add(p)
}
firstC := time.After(first.Sub(time.Now()))
// 从 nil 通道接收会永远阻塞
t := &time.Ticker{C: nil}
for {
select {
case v := <-firstC:
// 在调用 f 之前必须启动 ticker,因为 f 可能需要一些时间来完成
t = time.NewTicker(p)
f(v)
case v := <-t.C:
f(v)
case <-ctx.Done():
t.Stop()
return
}
}
}
原始版本:
package main
import (
"time"
)
// Repeat 使用周期 `d` 和偏移量 `o` 调用函数 `f`。
func Repeat(d time.Duration, o time.Duration, f func(time.Time)) {
next := time.Now().Truncate(d).Add(o)
if next.Before(time.Now()) {
next = next.Add(d)
}
t := time.NewTimer(next.Sub(time.Now()))
for {
v := <-t.C
next = next.Add(d)
t.Reset(next.Sub(time.Now()))
f(v)
}
}
英文:
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.
package main
import (
"context"
"time"
)
// Schedule calls function `f` with a period `p` offsetted by `o`.
func Schedule(ctx context.Context, p time.Duration, o time.Duration, f func(time.Time)) {
// Position the first execution
first := time.Now().Truncate(p).Add(o)
if first.Before(time.Now()) {
first = first.Add(p)
}
firstC := time.After(first.Sub(time.Now()))
// Receiving from a nil channel blocks forever
t := &time.Ticker{C: nil}
for {
select {
case v := <-firstC:
// The ticker has to be started before f as it can take some time to finish
t = time.NewTicker(p)
f(v)
case v := <-t.C:
f(v)
case <-ctx.Done():
t.Stop()
return
}
}
}
Original:
package main
import (
"time"
)
// Repeat calls function `f` with a period `d` offsetted by `o`.
func Repeat(d time.Duration, o time.Duration, f func(time.Time)) {
next := time.Now().Truncate(d).Add(o)
if next.Before(time.Now()) {
next = next.Add(d)
}
t := time.NewTimer(next.Sub(time.Now()))
for {
v := <-t.C
next = next.Add(d)
t.Reset(next.Sub(time.Now()))
f(v)
}
}
答案6
得分: 0
我正在使用https://github.com/ehsaniara/gointerlock。它还支持分布式系统,并且具有内置的分布式锁(Redis)。
import (
"context"
"fmt"
"github.com/ehsaniara/gointerlock"
"log"
"time"
)
var job = gointerlock.GoInterval{
Interval: 2 * time.Second,
Arg: myJob,
}
err := job.Run(ctx)
if err != nil {
log.Fatalf("Error: %s", err)
}
func myJob() {
fmt.Println(time.Now(), " - called")
}
英文:
I'm using https://github.com/ehsaniara/gointerlock. It's also supported in distributed systems and has a builtin distributer lock (Redis)
import (
"context"
"fmt"
"github.com/ehsaniara/gointerlock"
"log"
"time"
)
var job = gointerlock.GoInterval{
Interval: 2 * time.Second,
Arg: myJob,
}
err := job.Run(ctx)
if err != nil {
log.Fatalf("Error: %s", err)
}
func myJob() {
fmt.Println(time.Now(), " - called")
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论