Memory model
Concurrent correctness depends on happens-before relationships, not intuition.
Canonical guidance
- if data is accessed concurrently, synchronization must establish ordering
- channel operations, mutexes, and atomic operations matter because they create happens-before edges
- absence of crashes does not prove correctness
Use when
- shared-memory concurrency
- lock-free or atomic code
- explaining visibility bugs
Avoid
- assuming writes become visible “soon enough”
- hand-wavy reasoning about goroutine interleavings
- writing concurrent code without understanding which operation synchronizes with which
Preferred pattern
type Config struct {
Addr string
}
func LoadConfig() Config {
var cfg Config
ready := make(chan struct{})
go func() {
cfg = loadConfig()
close(ready)
}()
<-ready
return cfg
}
Anti-pattern
- sharing mutable state across goroutines with no synchronization because tests happened to pass
Explanation: This anti-pattern is common when tests seem stable, but visibility bugs can stay hidden until production interleavings expose them.
Why
- concurrency bugs are often visibility and ordering bugs, not only race-detector bugs
Sources
- The Go Memory Model - Go Team
- Data Race Detector - Go Team