Use a sync.WaitGroup combined with a buffered channel to limit concurrent goroutines, or use context.WithCancel to propagate cancellation signals. The buffered channel acts as a semaphore to enforce the concurrency limit, while the context ensures all workers stop when the parent operation completes.
func boundedWork(ctx context.Context, jobs []Job, maxWorkers int) {
var wg sync.WaitGroup
sem := make(chan struct{}, maxWorkers)
for _, job := range jobs {
select {
case <-ctx.Done():
return
default:
}
sem <- struct{}{}
wg.Add(1)
go func(j Job) {
defer wg.Done()
defer func() { <-sem }()
if err := doWork(ctx, j); err != nil {
log.Println(err)
}
}(job)
}
wg.Wait()
}