The three patterns
Most real-world Go concurrency fits into three shapes: fan-out/fan-in, worker pools, and pipelines.
Fan-out / fan-in
Launch N goroutines for independent work, collect results via a channel:
results := make(chan Result, len(items))
for _, item := range items {
go func(i Item) { results <- process(i) }(item)
}
for range items {
r := <-results
// handle r
}
Worker pools
Cap concurrency at N workers. Use a buffered channel as a semaphore:
sem := make(chan struct{}, maxWorkers)
for _, item := range items {
sem <- struct{}{}
go func(i Item) {
defer func() { <-sem }()
process(i)
}(item)
}
Context cancellation
Always accept a context.Context and respect it:
func doWork(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
case result := <-workCh:
return handle(result)
}
}
Pass the context down — never store it in a struct.