Как выполнять повторяющиеся задачи с заданным интервалом в Go?
Использование time.NewTicker для периодических задач
Да, в Go есть элегантное решение — пакет time предоставляет тип Ticker. Он создает канал, который отправляет текущее время через заданные интервалы. Это позволяет не блокировать горутину полностью и корректно завершать работу.
Вот как можно переписать ваш пример с использованием тикера:
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
// Создаем тикер с интервалом 5 секунд
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop() // освобождаем ресурсы при завершении
// Канал для сигналов ОС (Ctrl+C, SIGTERM)
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
fmt.Println("Мониторинг запущен. Нажмите Ctrl+C для остановки.")
// Бесконечный цикл с select
for {
select {
case <-ticker.C:
// Сработал тикер — выполняем задачу
fmt.Println("Проверяю сервер...")
// Здесь ваша логика проверки
case sig := <-sigChan:
// Получен сигнал завершения
fmt.Printf("Получен сигнал %v. Завершаю работу...\n", sig)
return
}
}
}
Как это работает
- time.NewTicker(d) создает тикер, который отправляет значение в канал
Cкаждыеd. - defer ticker.Stop() гарантирует, что тикер будет остановлен при выходе из функции, чтобы не было утечки горутин.
- select позволяет слушать несколько каналов одновременно. Когда срабатывает тикер — выполняется задача. Когда приходит сигнал — программа завершается.
- signal.Notify перенаправляет системные сигналы (SIGINT, SIGTERM) в канал, что позволяет корректно завершить программу без ожидания окончания таймера.
Дополнительные возможности
Если вам нужно выполнить задачу сразу при запуске, а затем повторять через интервал, используйте time.NewTicker в комбинации с первоначальным вызовом функции:
func main() {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
// Выполняем сразу
doWork()
for {
select {
case <-ticker.C:
doWork()
case <-sigChan:
return
}
}
}
func doWork() {
fmt.Println("Проверяю сервер...")
}
Также обратите внимание на time.Tick() — это упрощенная версия, но она не позволяет остановить тикер, поэтому для production-кода лучше использовать time.NewTicker.
Ваш исходный подход с time.Sleep работает, но не дает контроля над прерыванием. Тикер решает эту проблему и является идиоматичным способом в Go.
Альтернатива: time.Timer и рекурсивный подход
Если вам нужно более гибкое управление (например, динамически менять интервал или пропускать выполнение при ошибках), можно использовать time.Timer в комбинации с рекурсией через горутину:
func startWorker(interval time.Duration, stop chan struct{}) {
go func() {
timer := time.NewTimer(interval)
defer timer.Stop()
for {
select {
case <-timer.C:
// Выполняем работу
fmt.Println("Работаю...")
// Сбрасываем таймер для следующего выполнения
timer.Reset(interval)
case <-stop:
fmt.Println("Остановлен")
return
}
}
}()
}
func main() {
stop := make(chan struct{})
startWorker(5*time.Second, stop)
// Ждем сигнал
time.Sleep(20 * time.Second)
close(stop) // останавливаем воркер
time.Sleep(1 * time.Second)
}
Этот подход позволяет легко создавать несколько воркеров с разными интервалами и управлять ими через каналы. Однако он чуть сложнее, чем тикер, и требует ручного сброса таймера. Для простых повторяющихся задач лучше использовать time.NewTicker из первого ответа.