Введение
gron
Это относительно небольшая и гибкая библиотека задач синхронизации, которая может выполнять временные и периодические задачи.gron
Предоставляет лаконичный, защищенный от параллелизма интерфейс. Давайте сначала представимgron
Воспользуйтесь библиотекой, а затем кратко проанализируйте исходный код.
быстрый в использовании
Сначала установите:
$ go get github.com/roylee0704/gron
После использования:
package main
import (
"fmt"
"sync"
"time"
"github.com/roylee0704/gron"
)
func main() {
var wg sync.WaitGroup
wg.Add(1)
c := gron.New()
c.AddFunc(gron.Every(5*time.Second), func() {
fmt.Println("runs every 5 seconds.")
})
c.Start()
wg.Wait()
}
gron
относительно прост в использовании:
- первый звонок
gron.New()
Создаватьуправляющий делами, который является запланированным диспетчером задач; - Тогда звоните менеджеру
AddFunc()
илиAdd()
способ добавления к нему задач, также возможно добавление задач при запуске, см. анализ ниже; - Последний звонок менеджеру
Start()
метод его запуска.
gron
Поддерживаются два способа добавления задач, один из них — использоватьнет параметровДругой — реализовать интерфейс задачи. В приведенном выше примере используется первый метод, а метод реализации интерфейса мы представим позже. Пропускать при добавлении задачgron.Every()
Укажите интервал периодических задач, к нему добавляется периодическая задача 5с, и строка текста выводится каждые 5с.
Обратите внимание, что мы используемsync.WaitGroup
Убедитесь, что основная горутина не завершается. потому чтоc.Start()
Запускается только одна горутина, если основная горутина завершается, вся программа останавливается.
Запускаем программу и выводим каждые 5 секунд:
runs every 5 seconds.
runs every 5 seconds.
runs every 5 seconds.
Программа требует нажатияctrl + c
остановка!
Формат времени
gron
приниматьtime.Duration
тип интервала, кромеtime
Базы, определенные в пакетеSecond/Minute/Hour
,gron
серединаxtime
Подпакет также предоставляетDay/Week
единица времени. Одно замечание,gron
Поддерживаемая точность времени составляет 1 с, а интервалы менее 1 с не поддерживаются. В дополнение к единичным временным интервалам мы также можем использовать4m10s
Время такое:
func main() {
var wg sync.WaitGroup
wg.Add(1)
c := gron.New()
c.AddFunc(gron.Every(1*time.Second), func() {
fmt.Println("runs every second.")
})
c.AddFunc(gron.Every(1*time.Minute), func() {
fmt.Println("runs every minute.")
})
c.AddFunc(gron.Every(1*time.Hour), func() {
fmt.Println("runs every hour.")
})
c.AddFunc(gron.Every(1*xtime.Day), func() {
fmt.Println("runs every day.")
})
c.AddFunc(gron.Every(1*xtime.Week), func() {
fmt.Println("runs every week.")
})
t, _ := time.ParseDuration("4m10s")
c.AddFunc(gron.Every(t), func() {
fmt.Println("runs every 4 minutes 10 seconds.")
})
c.Start()
wg.Wait()
}
пройти черезgron.Every()
Установите, как часто задача должна выполняться. Для интервалов больше 1 дня мы также можем использоватьgron.Every().At()
Указывает, что он выполняется в определенный момент времени. Например, следующая программа, начиная со второго дня22:00
Старт, триггер через день, т.е. каждый день22:00
вызывать:
func main() {
var wg sync.WaitGroup
wg.Add(1)
c := gron.New()
c.AddFunc(gron.Every(1*xtime.Day).At("22:00"), func() {
fmt.Println("runs every second.")
})
c.Start()
wg.Wait()
}
пользовательская задача
Реализовать пользовательские задачи тоже очень просто, нужно всего лишь реализоватьgron.Job
Интерфейс может быть:
// src/github.com/roylee0704/gron/cron.go
type Job interface {
Run()
}
Нам нужно вызвать планировщикAdd()
Способ добавления кастомной задачи в менеджер:
type GreetingJob struct {
Name string
}
func (g GreetingJob) Run() {
fmt.Println("Hello ", g.Name)
}
func main() {
var wg sync.WaitGroup
wg.Add(1)
g1 := GreetingJob{Name: "dj"}
g2 := GreetingJob{Name: "dajun"}
c := gron.New()
c.Add(gron.Every(5*time.Second), g1)
c.Add(gron.Every(10*time.Second), g2)
c.Start()
wg.Wait()
}
Выше мы написалиGreetingJob
структура, реализацияgron.Job
интерфейс, затем создайте два объектаg1/g2
, 5-секундный триггер один раз, 10-секундный триггер один раз. Задачи, которые содержат состояние, лучше обрабатывать с помощью пользовательских задач, таких как приведенные выше.Name
поле.
По факту,AddFunc()
Внутри также передается методAdd()
Реализовано:
// src/github.com/roylee0704/gron/cron.go
func (c *Cron) AddFunc(s Schedule, j func()) {
c.Add(s, JobFunc(j))
}
type JobFunc func()
func (j JobFunc) Run() {
j()
}
существуетAddFunc()
Внутренне преобразовать входящую функцию вJobFunc
тип, покаgron
заJobFunc
Достигнутоgron.Job
интерфейс. Это сnet/http
в упаковкеHandleFunc
а такжеHandle
Так же, как. Если вы обратите внимание, такие шаблоны есть во многих кодах Go.
немного исходного кода
gron
Исходный код имеет только два файлаcron.go
а такжеschedule.go
,cron.go
Реализовать методы для добавления задач и планирования вschedule.go
Это код, связанный со стратегией времени. Всего в двух файлах всего 260 строк, включая комментарии! Добавленные задачи находятся вgron
Внутри находятсяEntry
Структура представляет:
type Entry struct {
Schedule Schedule
Job Job
Next time.Time
Prev time.Time
}
Next
следующее время выполнения,Prev
время последнего выполнения,Job
задача, которую нужно выполнить,Schedule
заgron.Schedule
тип интерфейса, назовите егоNext()
Момент времени следующего выполнения может быть рассчитан.
использование менеджераgron.Cron
Структура означает:
type Cron struct {
entries []*Entry
running bool
add chan *Entry
stop chan struct{}
}
Планирование задач выполняется в другой горутине. Если планирование еще не началось, добавление задач можно выполнить напрямую.append
прибытьentries
Нарезка; если планирование началось (Start()
был вызван метод), его нужно отправить в каналadd
Отправить задачу для добавления. Основная логика планирования задачRun()
В методе:
func (c *Cron) run() {
var effective time.Time
now := time.Now().Local()
// to figure next trig time for entries, referenced from now
for _, e := range c.entries {
e.Next = e.Schedule.Next(now)
}
for {
sort.Sort(byTime(c.entries))
if len(c.entries) > 0 {
effective = c.entries[0].Next
} else {
effective = now.AddDate(15, 0, 0) // to prevent phantom jobs.
}
select {
case now = <-after(effective.Sub(now)):
// entries with same time gets run.
for _, entry := range c.entries {
if entry.Next != effective {
break
}
entry.Prev = now
entry.Next = entry.Schedule.Next(now)
go entry.Job.Run()
}
case e := <-c.add:
e.Next = e.Schedule.Next(time.Now())
c.entries = append(c.entries, e)
case <-c.stop:
return // terminate go-routine.
}
}
}
Последовательность выполнения следующая:
- Когда планировщик только запущен, он сначала рассчитывает следующее время выполнения всех задач;
- затем в
for
В цикле отсортируйте по времени выполнения от раннего к позднему и вынесите момент времени, когда задачу нужно выполнить недавно; - существует
select
В операторе дождитесь этого момента времени, запустите новую горутину для выполнения задач с истекшим сроком действия, и у каждой задачи будет новая горутина; - Если в процессе ожидания добавляется новая задача (через канал
c.add
), чтобы рассчитать первое время выполнения этой новой задачи. Перейдите к шагу 2, так как новые добавленные задачи могут выполняться раньше.
Следует отметить несколько деталей:
- Оценка срока действия задачи использует местное время:
time.Now().Local()
; - Если задач нет, время ожидания устанавливается равным
now.AddDate(15, 0, 0)
, т.е. 15 лет, чтобы ЦП не простаивал; - Задачи выполняются в независимых горутинах;
- путем реализации
sort.Interface
В интерфейсе может быть реализована пользовательская сортировка (в кодеbyTime
).
Наконец, давайте посмотрим на код временной стратегии. мы знаем, что вEntry
В структуре хранитсяgron.Schedule
объект типа, вызывая объектNext()
Метод возвращает момент времени следующего выполнения:
// src/github.com/roylee0704/gron/schedule.go
type Schedule interface {
Next(t time.Time) time.Time
}
gron
Две встроенные реализацииSchedule
, одинperiodicSchedule
,СейчасПериодический триггер,gron.Every()
Функция возвращает этот объект:
// src/github.com/roylee0704/gron/schedule.go
type periodicSchedule struct {
period time.Duration
}
одинПериодический запуск в фиксированное время, это тоже периодический триггер, но момент времени фиксированный:
type atSchedule struct {
period time.Duration
hh int
mm int
}
Их основная логика заключается вNext()
метод,periodicSchedule
Просто добавьте период к текущему времени, чтобы получить следующее время запуска. здесьTruncate()
Метод усекает часть текущего времени, которая меньше 1 с:
func (ps periodicSchedule) Next(t time.Time) time.Time {
return t.Truncate(time.Second).Add(ps.period)
}
atSchedule
изNext()
Метод сначала вычисляет временную точку дня, плюс цикл — это время следующего триггера:
func (as atSchedule) reset(t time.Time) time.Time {
return time.Date(t.Year(), t.Month(), t.Day(), as.hh, as.mm, 0, 0, time.UTC)
}
func (as atSchedule) Next(t time.Time) time.Time {
next := as.reset(t)
if t.After(next) {
return next.Add(as.period)
}
return next
}
periodicSchedule
при условииAt()
метод может быть преобразован вatSchedule
:
func (ps periodicSchedule) At(t string) Schedule {
if ps.period < xtime.Day {
panic("period must be at least in days")
}
// parse t naively
h, m, err := parse(t)
if err != nil {
panic(err.Error())
}
return &atSchedule{
period: ps.period,
hh: h,
mm: m,
}
}
индивидуальная временная стратегия
Мы можем легко реализовать пользовательскую временную стратегию. Например, мы хотим реализовать временной ряд с «экспоненциальным отставанием», сначала подождите 1 с, затем 2 с, 4 с...
type ExponentialBackOffSchedule struct {
last int
}
func (e *ExponentialBackOffSchedule) Next(t time.Time) time.Time {
interval := time.Duration(math.Pow(2.0, float64(e.last))) * time.Second
e.last += 1
return t.Truncate(time.Second).Add(interval)
}
func main() {
var wg sync.WaitGroup
wg.Add(1)
c := gron.New()
c.AddFunc(&ExponentialBackOffSchedule{}, func() {
fmt.Println(time.Now().Local().Format("2006-01-02 15:04:05"), "hello")
})
c.Start()
wg.Wait()
}
Результаты приведены ниже:
2020-04-20 23:47:11 hello
2020-04-20 23:47:13 hello
2020-04-20 23:47:17 hello
2020-04-20 23:47:25 hello
Второй вывод отличается от первого на 2 с, третий от второго на 4 с, а четвертый от третьего на 8 с, идеально!
Суммировать
В этой статье описываетсяgron
Как использовать эту небольшую библиотеку задач на время, как настраивать задачи и временные стратегии, и, кстати, анализировать исходный код.gron
Реализация исходного кода очень лаконична, и ее настоятельно рекомендуется прочитать!
Если вы найдете забавную и простую в использовании языковую библиотеку Go, добро пожаловать, чтобы отправить вопрос на Go Daily Library GitHub😄
Ссылаться на
- гитхаб:GitHub.com/Рой Ли0704/…
- Перейти на ежедневный репозиторий GitHub:GitHub.com/Darenjun/go-of…
я
мой блог:darjun.github.io
Добро пожаловать, чтобы обратить внимание на мою общедоступную учетную запись WeChat [GoUpUp], учитесь вместе и добивайтесь прогресса вместе ~