Ежедневная библиотека Грона Го

Go

Введение

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.
    }
  }
}

Последовательность выполнения следующая:

  1. Когда планировщик только запущен, он сначала рассчитывает следующее время выполнения всех задач;
  2. затем вforВ цикле отсортируйте по времени выполнения от раннего к позднему и вынесите момент времени, когда задачу нужно выполнить недавно;
  3. существуетselectВ операторе дождитесь этого момента времени, запустите новую горутину для выполнения задач с истекшим сроком действия, и у каждой задачи будет новая горутина;
  4. Если в процессе ожидания добавляется новая задача (через каналc.add), чтобы рассчитать первое время выполнения этой новой задачи. Перейдите к шагу 2, так как новые добавленные задачи могут выполняться раньше.

Следует отметить несколько деталей:

  1. Оценка срока действия задачи использует местное время:time.Now().Local();
  2. Если задач нет, время ожидания устанавливается равнымnow.AddDate(15, 0, 0), т.е. 15 лет, чтобы ЦП не простаивал;
  3. Задачи выполняются в независимых горутинах;
  4. путем реализации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😄

Ссылаться на

  1. гитхаб:GitHub.com/Рой Ли0704/…
  2. Перейти на ежедневный репозиторий GitHub:GitHub.com/Darenjun/go-of…

я

мой блог:darjun.github.io

Добро пожаловать, чтобы обратить внимание на мою общедоступную учетную запись WeChat [GoUpUp], учитесь вместе и добивайтесь прогресса вместе ~