Нужен планировщик, который запускает зарегистрированные функции по расписанию,
у каждой свой интервал. Выполняются они не в отдельной горутине на каждый
запуск, а на общем пуле из workers воркеров.
func NewScheduler(workers int) *Scheduler
// Add регистрирует задачу: запускать job каждые interval.
func (s *Scheduler) Add(interval time.Duration, job func())
// Start запускает воркеров и расписание.
func (s *Scheduler) Start()
// Stop останавливает планировщик и дожидается воркеров.
func (s *Scheduler) Stop()
Требования:
- Задача с интервалом
interval запускается примерно раз в interval. Первый
запуск происходит через один интервал после Start, а не сразу.
- Выполнение идёт на фиксированном пуле из
workers воркеров. По горутине на
каждый тик заводить нельзя.
Stop прекращает планирование, дожидается задач, которые уже поставлены в
очередь или выполняются, и завершает всех воркеров. После возврата из Stop
задачи больше не запускаются, горутины не утекают, паники нет.
- Код безопасен под
go test -race.
Пример:
s := NewScheduler(2)
s.Add(100*time.Millisecond, func() { fmt.Println("tick") })
s.Start()
time.Sleep(350 * time.Millisecond)
s.Stop()
Задача сработает на 100, 200 и 300 мс, поэтому tick напечатается три раза.
После Stop вывод больше не растёт.
Допущения. Все Add вызываются до единственного Start. Интервалы и workers
положительны. Функции задач в итоге возвращаются.
Тесты проверяют: частая задача срабатывает заметно чаще редкой; первый запуск происходит не раньше чем через интервал после Start; одновременно выполняется не больше workers функций, хотя тиков намного больше; Stop дожидается уже выполняющихся задач, после него ничего не запускается и горутины не утекают.
Что проверяют на собеседовании: умеете ли вы развести «когда запускать» и
«кто выполняет», и в каком порядке нужно гасить источники и потребителей,
чтобы не получить запись в закрытый канал.