2021-01-04 16:29:36
Небольшая заметка о том, как не нужно ожидать горутины в golang. Возьмем простую ситуацию — нужно написать программу, которая раз в 2 секунды в фоне отсылает какие-то данные в сторонний сервис. Для отправки по интервалу будем использовать time.Ticker:
func main() {
ticker := time.NewTicker(2 * time.Second)
// Запускаем таймер в фоне
go func() {
for range ticker.C {
log.Printf("Sending data to external service")
}
}()
// Выполняем основную логику
go doSomeLogic();
}
Но возникает проблема, главная горутина закончится быстрее, чем начнется фоновая. Чтобы это пофиксить, необходимо
заблокировать главную горутину и сделать это можно кучей способов, но есть 1 неправильный, которые встречается в мире разработки:
// Входим в бесконечный цикл
for {
}
Цикл
не означает, что горутина перейдет в режим ожидания или отдаст процессорное время другим горутинам. Наоборот, цикл будет постоянно крутится и нагружать CPU. Некоторые IDE, типа
Goland, предупреждают об этом. Если на проекте используется версия golang <
1.14, то при одном CPU,
не будет выполняться ни 1 горутина кроме бесконечного цикла.
Поправить это несколькими вариантами. Самый простой, но говнокод:
// Входим в бесконечный цикл
for {
time.Sleep(1 * time.Minute)
}
Здесь ситуация уже получше, потому что цикл будет крутиться не постоянно, а делать паузы. Если пауза будет на несколько миллисекунд, то нагрузка на CPU вернется. Плюс, это решит проблему для golang <
1.14, потому что вызов функции позволит рантайму перейти к выполнению других горутин.
Есть еще несколько способов с использованием sync.waitGroup.wait(), for с пустым select {}, но когда мы говорим о главном цикле приложения, наиболее предпочтительным вариантом в данной ситуации будет дождаться сигнала и завершиться:
func makeSignalShutdownChan() chan os.Signal {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
return c
}
// ...
<-makeSignalShutdownChan()
Ожидание сообщений из сигналов работает на порядок эффективнее бесконечных циклов.
#golang #backend
75 views13:29