Как программа узнает об окончании списка URL и передает сигнал завершения
Что проверяют
Понимаете ли вы идиомы завершения в Go: кто закрывает канал, как воркеры узнают, что работы больше нет, и как главная горутина дожидается всех. Следом спрашивают, как остановить обработку досрочно при ошибке или таймауте.
Как отвечать
Сигнал окончания списка в Go это закрытие канала. Каркас пайплайна:
- продюсер: отправляет все URL в канал
jobsи вызываетclose(jobs). Закрывает канал только отправитель и ровно один раз; - воркеры: читают через
for u := range jobs. Цикл завершается сам, когда канал закрыт и опустел, никаких «стоп-значений» не нужно; - ожидание воркеров:
sync.WaitGroup,wg.Add(1)перед запуском горутины,defer wg.Done()внутри. В Go 1.25 появилсяwg.Go(f), который делает то же самое короче; - закрытие результатов: отдельная горутина делает
wg.Wait()иclose(results), а главная читаетfor r := range results. Цикл кончился, значит всё обработано; - досрочная остановка:
context.WithCancelилиWithTimeout, воркеры вselectпроверяютctx.Done(). Удобная обёртка этоerrgroupизgolang.org/x/sync: первая ошибка отменяет контекст,g.Wait()возвращает её, аSetLimitограничивает число горутин.
Для сигнала без данных используют chan struct{}: его закрытие видят все читатели сразу, это широковещательное оповещение.
Полезно назвать правила каналов: чтение из закрытого канала сразу возвращает нулевое значение и ok == false, запись в закрытый канал вызывает панику, повторное закрытие тоже.
Где спотыкаются
Вызывают wg.Add(1) внутри уже запущенной горутины. Wait может выполниться раньше, чем счётчик увеличится, и программа завершится, не дождавшись работы.
Закрывают канал со стороны воркеров или из нескольких мест. Первый close проходит, второй роняет программу паникой.
Забывают закрыть results или выходят из главного цикла по первой ошибке, не отменив контекст. В первом случае рантайм падает с fatal error: all goroutines are asleep, во втором горутины навсегда застревают на отправке и утекают.