Golang Concurrency
Priority: P0 (CRITICAL)
Principles
- Share Memory by Communicating: Use channels or synchronization primitives instead of unprotected shared memory.
- Context Propagation: Always pass
ctxto manage cancellation, deadlines, and graceful termination. - Throttle Database Load: Reserve goroutines for independent external API calls or CPU work. Avoid unconstrained goroutines hitting databases to prevent connection pool exhaustion.
- Prevent Leaks: Never start a goroutine without a deterministic shutdown mechanism.
- Race Detection: Always verify concurrent code with
go test -race.
Implementation Workflow
- Choose primitive —
errgroup.Groupfor parallel tasks with error propagation, channels for pipelines,sync.Mutexfor simple shared state. - Bound Concurrency with Semaphore — Cap maximum active goroutines using a buffered channel semaphore:
sem := make(chan struct{}, maxWorkers) for _, task := range tasks { task := task // prevent loop pointer capture sem <- struct{}{} g.Go(func() error { defer func() { <-sem }() return task.Execute(ctx) }) } - Respect Context Cancellation — Goroutines performing I/O must honor
ctx.Done(). - Avoid Loop Pointer Capture — Capture iteration variables explicitly before launching goroutines.
- Test with race detector — Run
go test -racein local dev and CI.
See ErrGroup and concurrency patterns and context timeout examples
Anti-Patterns
- No DB connection spam: avoid spawning unbounded goroutines to query the database in parallel.
- No goroutine leaks: ensure every goroutine has an exit path via
ctx.Done()or channel closure. - No loop variable pointer trap: never pass the address of a loop variable (
&item) into a concurrent closure. - No unbuffered goroutine spawn storms: always cap concurrency with worker pools or semaphores.
- No bare goroutines: use
errgrouporsync.WaitGroupfor lifecycle tracking.