Go Concurrency Quick Reference
Profiling Commands
| Profile |
Command |
Shows |
| Block |
go test -blockprofile=b.prof |
Where goroutines wait (channels, mutexes, I/O) |
| Mutex |
go test -mutexprofile=m.prof |
Lock contention time |
| Goroutine |
go tool pprof http://host:port/debug/pprof/goroutine |
Goroutine stack dump |
| Trace |
go test -trace=t.out |
Per-goroutine timeline |
| Race |
go test -race ./... |
Data races (mandatory after changes) |
Sync Primitives Comparison
| Primitive |
Use case |
Overhead |
Notes |
sync.Mutex |
Mutual exclusion |
~20ns uncontended |
Fastest for exclusive access |
sync.RWMutex |
Read-heavy |
~25ns read, ~30ns write |
N concurrent readers |
chan struct{} |
Signaling |
~50ns send/recv |
Use for coordination, not data |
chan T (buffered) |
Producer/consumer |
~50ns |
Buffer size = concurrency slack |
sync.Once |
Init once |
~1ns after first call |
Perfect for lazy init |
sync.Pool |
Object reuse |
~50ns get/put |
GC may reclaim objects |
sync.WaitGroup |
Wait for N goroutines |
~20ns per Add/Done |
Simple fork-join |
errgroup.Group |
Wait + error + limit |
~50ns |
Preferred over raw WaitGroup |
atomic.Value |
Lock-free read/write |
~5ns |
For values rarely written |
Atomic vs Mutex Performance
| Operation |
Atomic |
Mutex |
Delta |
| Simple increment |
80.4 ns/op |
110.7 ns/op |
Atomic 27% faster |
| Read flag |
~5 ns |
~20 ns |
Atomic 4× faster |
| CAS (compare-and-swap) |
~10 ns |
N/A |
Lock-free alternative |
Use atomics for: counters, flags, CAS loops, lock-free stacks/queues.
Use mutexes for: complex state, multi-step critical sections, invariant enforcement.
Worker Pool vs Unbounded Goroutines
| Approach |
Performance |
Resource Usage |
Unbounded go func() |
Baseline |
Unpredictable, OOM risk |
| Worker pool (errgroup) |
28% faster |
Bounded, predictable |
Goroutine Leak Checklist
- Every
go func() must have a termination path
- Every channel send must have a receiver (or use
select with ctx.Done())
- Every
time.After in a loop should be replaced with time.NewTimer + Reset
- Every HTTP client must have a timeout:
&http.Client{Timeout: 10 * time.Second}
- Every context must be cancelled:
ctx, cancel := context.WithCancel(parent); defer cancel()