Golang: Реализация cron/выполнение задач в определенное время

Я искал примеры того, как реализовать функцию, позволяющую выполнять задачи в определенное время в Go, но ничего не нашел.

Я реализовал один сам и делюсь им в ответах, чтобы другие люди могли иметь ссылку на свою собственную реализацию.


person Daniele B    schedule 23.10.2013    source источник
comment
бесстыдный плагин: возможно, вы могли бы проверить это: минималистский пакет cron go-package: github.com/roylee0704/gron   -  person RLEE    schedule 14.06.2016


Ответы (6)


Это общая реализация, которая позволяет вам установить:

  • интервальный период
  • час, чтобы тикать
  • минута до отметки
  • второй до галочки

ОБНОВЛЕНО: (устранена утечка памяти)

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)
    }
}
person Daniele B    schedule 23.10.2013
comment
В этой реализации происходит утечка памяти. › Остановите бегущую строку, чтобы освободить связанные ресурсы. - person Caleb; 27.02.2016
comment
@Caleb прав, каждый раз, когда мы создаем новый тикер, старый никогда не будет выпущен. лучшее решение здесь: stackoverflow.com/a/39295990/2791115 - person simon_xia; 02.09.2016
comment
@Калеб, спасибо! не стесняйтесь обновлять код ответа, чтобы исправить утечку - person Daniele B; 10.08.2017
comment
разве там не собран мусор? - person An Phung; 09.03.2018
comment
Это плохо справляется с настройкой часов NTP ... иногда дневной тикер срабатывает дважды. - person rustyx; 07.05.2020

ответ, предоставленный @Daniele B, недостаточно хорош, поскольку @Caleb говорит, что реализация дает утечку памяти, потому что каждый раз, когда мы создаем новый тикер, старый никогда не будет выпущен.

поэтому я оборачиваю 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()
    }
}
person simon_xia    schedule 02.09.2016

На случай, если кто-то зайдет по этому вопросу в поисках быстрого решения. Я нашел классную библиотеку, которая позволяет очень легко планировать задания.

Ссылка: https://github.com/jasonlvhit/gocron

API довольно прост:

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()
}
person Mani    schedule 11.02.2016

Я создал пакет, который на самом деле поддерживает синтаксис crontab, если вы с ним знакомы, например:

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

Ссылка на пакет: https://github.com/mileusna/crontab

person mileusna    schedule 10.08.2017
comment
Это на самом деле очень приятно! Спасибо за создание этого пакета :) - person Bijan; 30.09.2020

Это еще одна общая реализация, не требующая сторонней библиотеки.

Отказ от ответственности: эта реализация работает с UTC. Для управления часовыми поясами его необходимо изменить.

Запускайте func один раз в день в полдень.

  • Период: time.Hour * 24
  • Смещение: time.Hour * 12

Запускайте func два раза в день в 03:40 (00:00 + 03:40) и 15:40 (12:00 + 03:40).

  • Период: time.Hour * 12
  • Смещение: time.Hour * 3 + time.Minute * 40

Обновлено (2020-01-28):

Изменения:

  • context.Context можно использовать для отмены, что делает его проверяемым.
  • time.Ticker устраняет необходимость расчета времени следующего выполнения.
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
        }
    }

}

Оригинал:

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)
    }
}
person Nergal    schedule 15.05.2019

Я использую 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")
}
person Jay    schedule 31.07.2021