Как послать сигнал в канал, чтобы все горутины получили уведомление о завершении работы в Go
Что проверяют
Понимаете ли вы, что отправка значения в канал будит только одного получателя, а закрытие канала будит всех. Следом почти всегда спрашивают, чем это отличается от context и как дождаться, пока горутины действительно завершатся.
Как отвечать
Ключевой механизм: чтение из закрытого канала не блокируется и сразу возвращает нулевое значение с ok == false. Поэтому широковещательный сигнал в Go делается через close, а не через отправку:
- канал
chan struct{}: пустая структура не занимает памяти и ясно показывает, что важен сам факт сигнала, а не данные; close(done): все горутины, которые ждут<-doneвselect, разблокируются разом, сколько бы их ни было;- закрывает один владелец: повторный
closeи отправка в закрытый канал вызывают панику. Если закрыть могут из нескольких мест, оберните вызов вsync.Once; context.WithCancel: идиоматичный вариант того же.ctx.Done()возвращает канал, который закрывается при вызовеcancel(), а сам контекст передаётся вниз по вызовам, в HTTP-клиент и драйверы баз. Причину отмены передаётcontext.WithCancelCause;- ожидание завершения: сигнал только просит остановиться, дождаться выхода помогает
sync.WaitGroup. С Go 1.25 у него есть методGo, который сам делаетAddиDone. Если нужна ещё и первая ошибка, беритеerrgroupизgolang.org/x/sync; - сигналы ОС:
signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)сразу даёт контекст, который отменится по Ctrl+C или SIGTERM.
sync.Cond с методом Broadcast тоже будит всех, но в прикладном коде им почти не пользуются: канал проще сочетать с select и таймаутами.
Где спотыкаются
Отправляют значение в цикле по числу горутин. Нужно точно знать их количество, а если какая-то уже вышла, отправитель зависнет навсегда.
Закрывают канал со стороны читателя или из нескольких горутин. Первый же повторный close роняет программу.
Проверяют done только в начале итерации, а внутри висят на блокирующей операции: чтении из другого канала или сетевом вызове. Сигнал такая горутина увидит, только когда операция вернётся, поэтому done нужно слушать в том же select, что и работу.