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

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

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 (
&quot;fmt&quot;
&quot;time&quot;
)

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 := &amp;jobTicker{}
    jobTicker.updateTimer()
    for {
        &lt;-jobTicker.timer.C
        fmt.Println(time.Now(), &quot;- just ticked&quot;)
        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, &quot;- next tick&quot;)
    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 (
    &quot;fmt&quot;
    &quot;github.com/jasonlvhit/gocron&quot;
)

func task() {
    fmt.Println(&quot;Task is being performed.&quot;)
}

func main() {
    s := gocron.NewScheduler()
    s.Every(2).Hours().Do(task)
    &lt;- 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 (
	&quot;fmt&quot;
	&quot;time&quot;
)

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(&quot;new tick here&quot;)
	return jobTicker{time.NewTimer(getNextTickDuration())}
}

func (jt jobTicker) updateJobTicker() {
	fmt.Println(&quot;next tick here&quot;)
	jt.t.Reset(getNextTickDuration())
}

func main() {
	jt := NewJobTicker()
	for {
		&lt;-jt.t.C
		fmt.Println(time.Now(), &quot;- just ticked&quot;)
		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(&quot;*/5 * * * *&quot;, myFunc)
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 可以避免计算下次执行的时间。
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 (
	&quot;context&quot;
	&quot;time&quot;
)

// 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 := &amp;time.Ticker{C: nil}

	for {
		select {
		case v := &lt;-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 := &lt;-t.C:
			f(v)
		case &lt;-ctx.Done():
			t.Stop()
			return
		}
	}

}

Original:

package main

import (
	&quot;time&quot;
)

// 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 := &lt;-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 (
	&quot;context&quot;
    &quot;fmt&quot;
	&quot;github.com/ehsaniara/gointerlock&quot;
    &quot;log&quot;
	&quot;time&quot;
)

var job = gointerlock.GoInterval{
    Interval: 2 * time.Second,
    Arg:      myJob,
}

err := job.Run(ctx)
if err != nil {
    log.Fatalf(&quot;Error: %s&quot;, err)
}

func myJob() {
    fmt.Println(time.Now(), &quot; - called&quot;)
}

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:

确定