Goroutines
Start goroutines for independently owned work with a clear lifecycle, not as anonymous background side effects.
Canonical guidance
- use a goroutine when work can proceed independently
- make completion, cancellation, and error flow explicit
- treat
goas a lifecycle decision, not just syntax
Use when
- overlapping I/O waits
- pipeline stages
- bounded worker pools or sibling tasks
Avoid
- spawning a goroutine to avoid blocking without deciding who owns it
- hidden background retries
- starting unbounded goroutines per request or item
Preferred pattern
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error {
return doWork(ctx)
})
return g.Wait()
Anti-pattern
go doWork()with no cancellation, wait, or error observation
Explanation: This is tempting because it removes immediate blocking, but it usually just moves the problem into lifecycle bugs.
Why
- goroutines are lightweight, but leaked or ownerless work is still expensive
Related pages
Sources
- The Go Programming Language Specification - Go Team
- Concurrency is not parallelism - Rob Pike
- Go Concurrency Patterns: Context - Sameer Ajmani