Ответ
Да, для ограничения количества одновременно работающих горутин в Go обычно используются паттерны, основанные на каналах или специальных примитивах синхронизации.
1. Буферизированный канал (Паттерн "Семафор")
Это классический и самый простой способ. Канал с буфером используется как пул "разрешений" на запуск. Горутина может запуститься, только когда получит значение из канала.
2. Пакет golang.org/x/sync/semaphore
Этот пакет предоставляет более явную и читаемую реализацию семафора.
import (
"context"
"golang.org/x/sync/semaphore"
"sync"
)
// ...
var wg sync.WaitGroup
maxWorkers := int64(5)
sem := semaphore.NewWeighted(maxWorkers)
ctx := context.Background()
for i := 0; i < 20; i++ {
wg.Add(1)
// Запрашиваем "вес" 1. Блокирует, если семафор исчерпан.
if err := sem.Acquire(ctx, 1); err != nil {
// Обработка ошибки, если контекст отменен
wg.Done()
continue
}
go func(n int) {
defer wg.Done()
defer sem.Release(1) // Освобождаем "вес"
// Полезная работа
}(i)
}
wg.Wait()
3. Пакет golang.org/x/sync/errgroup
Для задач, где нужно не только ограничить параллелизм, но и обрабатывать ошибки, errgroup
является идеальным решением. Метод SetLimit
позволяет легко задать максимальное количество горутин.
import "golang.org/x/sync/errgroup"
// ...
g, ctx := errgroup.WithContext(context.Background())
g.SetLimit(5) // Устанавливаем лимит
for i := 0; i < 20; i++ {
job := i
g.Go(func() error {
// Полезная работа, которая может вернуть ошибку
return doWork(ctx, job)
})
}
// Ожидаем завершения и получаем первую возникшую ошибку
if err := g.Wait(); err != nil {
log.Fatal(err)
}