Implement a lock-free "store max" using compare-and-swap. What is the ABA problem, and does it affect Go?
func StoreMax(a *atomic.Int64, v int64) {
for {
cur := a.Load()
if v <= cur {
return
}
if a.CompareAndSwap(cur, v) {
return
}
// another goroutine changed it: reload and retry
}
}
A CAS loop reads the current value, computes a new one, and swaps only if the value is still unchanged. If the swap fails it retries. Under heavy contention the loop can spin many times. For a simple sum, Add is better because it maps to a single hardware instruction (LOCK XADD).
ABA problem: a value changes from A to B and back to A between your Load and your CAS. The CAS succeeds even though the state changed underneath you. This matters most for lock-free stacks, where a node gets popped, freed, reused and pushed again.
In Go: the garbage collector mostly removes the memory-reuse variant. A node's address cannot be recycled while your goroutine still holds a pointer to it. Logical ABA is still possible with integer values or with explicitly pooled nodes, for example through sync.Pool. Common fixes are version counters or immutable nodes.
Interview tip: say that you reach for lock-free code only after profiling. A mutex is usually fast enough and much easier to reason about.
More on Concurrency Patterns & sync
- Q229When should you use sync.Map instead of a map guarded by a Mutex?
- Q230What are typed atomics (atomic.Int64, atomic.Pointer[T]) and why prefer them over atomic.AddInt64 / atomic.Value?
- Q232Explain the Go memory model and "happens-before". What synchronization edges does Go guarantee?
- Q233What does this program print?
- Q234How does the race detector work, and what are its limitations? Is a "benign" data race ever OK?
- Q235Does this counter give the right answer with GOMAXPROCS=1? What about counter++ in general?