Deadlocks, livelocks, and starvation
Concurrent systems must make progress. Blocking, retrying, or selecting forever without progress is still failure.
Canonical guidance
- reason about progress, not just safety
- make blocking edges and wakeup paths explicit
- inspect fairness assumptions carefully
Use when
- reviewing lock ordering
- designing long-lived select loops
- debugging stuck or spinning systems
Avoid
- circular waits across channels or locks
- retry loops that never back off or yield meaningful state changes
- assuming fairness from the scheduler or
select
Preferred pattern
- a blocked goroutine either receives a signal, observes cancellation, or has a proven owner that will unblock it
Anti-pattern
- two goroutines each waiting for the other to drain or close first
Explanation: This is tempting because each side looks locally reasonable, but globally no one can make progress.
Why
- concurrent correctness includes liveness, not just mutual exclusion
Related pages
Sources
- Advanced Go Concurrency Patterns - Sameer Ajmani
- sync package - Go Team