История вопроса:
С самого начала язык Go предоставил пакет time, внутри которого основные функции для управления временем — time.Sleep и time.After. В отличие от языков с системным сном (System.sleep), Go реализует асинхронные таймеры через свои примитивы, что важно для многопоточной работы.
Проблема:
Часто разработчики неправильно используют time.Sleep для реализации пауз между задачами, что нежелательно в конкурентных программах. В go-парадигме правильнее строить управление ожиданием событий через каналы и time.After для интеграции с select/каналами.
Решение:
time.Sleep(d) блокирует текущую горутину на d времени, это прямой «сон». time.After(d) возвращает канал, на который через d появится событие-время. Последний вариант намного гибче в select по каналам, удобен для прерываемых ожиданий, таймаутов.
Пример кода:
ch := make(chan struct{}) go func() { time.Sleep(2 * time.Second) ch <- struct{}{} }() select { case <-ch: fmt.Println("done") case <-time.After(1 * time.Second): fmt.Println("timeout") }
Ключевые особенности:
Можно ли использовать time.Sleep для блокировки выполнения всей программы?
Нет, time.Sleep «усыпляет» только одну горутину, остальные продолжают работу.
Может ли time.After привести к утечке памяти, если канал не читается?
Да, таймер висит до тех пор, пока значение не считано — если нет чтения, сборщик мусора объект не удалит.
Пример кода:
func leak() { for { _ = time.After(time.Hour) } }
Как корректно отменить ожидание по time.After при неполучении события?
Лучше использовать time.Timer и вручную останавливать, если нужно завершить ожидание до срока:
t := time.NewTimer(time.Minute) if done { t.Stop() }
Горутина ждёт сигнал от работы, и на случай таймаута делает так:
time.Sleep(10*time.Second) doSomething()
Плюсы:
Минусы:
Код строится через select и time.After:
select { case <-workSignal: // Выполнить case <-time.After(10 * time.Second): // Сделать по таймауту }
Плюсы:
Минусы: