Как реализовать параллельную проверку элементов массива с ограничением в 10 одновременно работающих goroutine?
Что проверяют
Умеете ли вы ограничить параллелизм и корректно собрать результаты без гонок и дедлоков. Следом обычно спрашивают, как остановить всё при первой ошибке и как вернуть результаты в исходном порядке.
Как отвечать
Стандартных способов три:
- семафор на буферизованном канале:
sem := make(chan struct{}, 10), перед запуском горутиныsem <- struct{}{}, внутриdefer func() { <-sem }(). Горутина создаётся на каждый элемент, но работают одновременно не больше десяти; - пул воркеров: ровно 10 горутин читают индексы из канала
jobs, основной код пишет туда все задачи и закрывает канал,sync.WaitGroupждёт воркеров. Подходит, когда элементов миллионы; errgroupс лимитом: изgolang.org/x/sync/errgroup.g, ctx := errgroup.WithContext(ctx),g.SetLimit(10), затемg.Go(func() error {...})на каждый элемент иg.Wait(). Возвращает первую ошибку и отменяетctx, чтобы остальные проверки могли остановиться. Самый короткий ответ на собеседовании.
Результаты удобнее всего собирать в заранее выделенный срез: res := make([]bool, len(items)), каждая горутина пишет в свой res[i]. Мьютекс не нужен, потому что индексы разные, а порядок сохраняется сам. Канал результатов нужен, только если их надо обрабатывать потоком.
С Go 1.22 переменная цикла своя на каждой итерации, так что замыкание go func() { check(items[i]) }() внутри for i := range items безопасно. С Go 1.25 у WaitGroup есть метод Go, который сам делает Add и Done.
Где спотыкаются
Делают небуферизованный канал результатов и читают его только после цикла запуска. Десять горутин блокируются на отправке, не отпустив семафор, одиннадцатая итерация встаёт на sem, и программа падает с «all goroutines are asleep». Эта ошибка сидит во многих готовых решениях.
Вызывают wg.Add(1) внутри горутины, а не перед go. Тогда Wait может вернуться раньше, чем все проверки стартовали.
Забывают про ошибки и отмену: одна проверка зависает на сети, и без контекста с таймаутом зависает всё. Родственная ошибка: append в общий срез из горутин без синхронизации, которую сразу покажет go test -race.