Как реализуется раздача заданий и синхронизация горутин в воркерпуле в Go
Что проверяют
Умеете ли вы собрать пул так, чтобы он корректно завершался: без утёкших горутин, дедлоков и паники на записи в закрытый канал. Следом обычно просят добавить отмену через context и сбор ошибок.
Как отвечать
Каркас из трёх частей: канал задач, фиксированное число воркеров, канал результатов.
- раздача:
jobs := make(chan Job)читают N горутин черезfor job := range jobs. Свободный воркер сам забирает следующую задачу, отдельный диспетчер не нужен; - завершение: отправитель закрывает
jobs, когда задачи кончились. Закрывает всегда тот, кто пишет, а не читатели; - ожидание:
wg.Add(1)вызывается доgo worker(...), а внутри воркераdefer wg.Done(). Иначеwg.Wait()может проскочить раньше, чем горутина стартовала; - результаты: отдельная горутина делает
wg.Wait()и затемclose(results), а main читаетresultsчерезrange. Если ждать воркеров в main до чтения результатов, небуферизованный канал результатов даст дедлок; - отмена: воркер делает
selectпоctx.Done()и по каналу задач, отправитель тоже пишет вjobsчерезselect, чтобы не зависнуть навсегда.
Число воркеров подбирают под нагрузку: для CPU-задач около runtime.GOMAXPROCS(0), для сетевых больше, но с оглядкой на лимиты внешнего сервиса. Если нужна первая ошибка с отменой остальных, покажите errgroup.Group из golang.org/x/sync с SetLimit(n): это готовый пул с ограничением параллелизма. В свежих версиях Go у sync.WaitGroup есть метод Go(func()), который сам делает Add и Done.
Где спотыкаются
Закрывают канал задач из воркера или из нескольких мест. Повторное закрытие или запись в закрытый канал дают панику, поэтому закрывать канал должен единственный отправитель.
Вызывают wg.Add(1) внутри уже запущенной горутины. Возникает гонка, и Wait иногда возвращается до того, как вся работа сделана.
Запускают горутину на каждую задачу, «потому что они дешёвые». На миллионе задач это миллион одновременных запросов к базе или API, а пул нужен именно для того, чтобы ограничить параллелизм.