When should you use sync.Map instead of a map guarded by a Mutex?
Question 229MediumGo 1.22 to 1.25
The docs name two cases where sync.Map is better:
- A key is written once and then read many times, as in a grow-only cache.
- Multiple goroutines read, write and overwrite disjoint sets of keys.
Everywhere else, a plain map with a Mutex or RWMutex is usually faster and simpler, and it stays type-safe.
Go 1.24 reimplemented sync.Map on a concurrent hash-trie. It now scales much better for mixed workloads and no longer relies on the older read-only map plus dirty map design with promotion.
var sessions sync.Map // effectively map[string]*Session
s, loaded := sessions.LoadOrStore(id, newSession(id))
sess := s.(*Session)
sessions.CompareAndSwap(id, old, updated) // Go 1.20+
sessions.Range(func(k, v any) bool {
fmt.Println(k, v)
return true // false stops iteration
})
sessions.Clear() // Go 1.23+
Gotchas:
- It has no
Len. - Values are
any, which means type assertions everywhere. Wrap it in a small generic type to get type safety back. Rangeis not a consistent snapshot: each key is visited at most once, but concurrent writes may or may not be observed.LoadOrStore(k, expensive())evaluatesexpensive()on every call, even when the key already exists.
More on Concurrency Patterns & sync
- Q227When would you use sync.Cond, and why must Wait always be called in a loop?
- Q228How does sync.Pool work, how does it interact with the GC, and what are the common misuse pitfalls?
- Q230What are typed atomics (atomic.Int64, atomic.Pointer[T]) and why prefer them over atomic.AddInt64 / atomic.Value?
- Q231Implement a lock-free "store max" using compare-and-swap. What is the ABA problem, and does it affect Go?
- Q232Explain the Go memory model and "happens-before". What synchronization edges does Go guarantee?
- Q233What does this program print?