What is false sharing, how does it hurt concurrent Go code, and how do you fix it?
Question 256HardGo 1.22 to 1.25
CPUs keep memory coherent per cache line: 64 bytes on most x86-64 CPUs, 128 bytes on some arm64 and ppc64 chips. When two cores write different variables that sit on the same line, each write invalidates the other core's copy. The line bounces between cores, and code with no logical contention scales as if it had a lock.
// Each worker increments only its own slot, yet this scales badly:
// 8-byte counters share one 64-byte line.
type Stats struct {
perWorker [8]atomic.Int64
}
// Padded: every counter owns a full cache line.
type paddedCounter struct {
n atomic.Int64
_ [56]byte // 8 + 56 = 64 bytes
}
type PaddedStats struct {
perWorker [8]paddedCounter
}
func (s *PaddedStats) Inc(worker int) { s.perWorker[worker].n.Add(1) }
func (s *PaddedStats) Total() int64 {
var t int64
for i := range s.perWorker {
t += s.perWorker[i].n.Load()
}
return t
}
Notes:
golang.org/x/sys/cpu.CacheLinePadgives a pad sized for the target architecture, so you do not have to hard-code 56.- The same thing happens with hot struct fields: a frequently written counter next to a read-mostly field slows the readers down. Group fields by access pattern.
- Striped or sharded counters trade a slower
Total()for fast writes. That suits metrics, where writes vastly outnumber reads. - The runtime does this itself:
sync.Pool's per-P local storage is padded against false sharing.
How to prove it: run a benchmark with -cpu=1,2,4,8. If throughput falls as the core count rises even though goroutines touch disjoint data, suspect false sharing; perf c2c on Linux confirms it. Padding costs memory, so only add it where a benchmark shows a gain.
More on Concurrency Patterns & sync
- Q254How do you test concurrent and time-dependent code deterministically? (testing/synctest)
- Q255What does this print? Can recover in main catch a panic from another goroutine?
- Q257How is GOMAXPROCS chosen in containers, and what changed in Go 1.25?
- Q258Implement a pub/sub broadcaster where one slow subscriber cannot block the others.