Go

When should you use sync.Map instead of a map with a mutex?

Question 60HardGo 1.22 to 1.25

The sync.Map docs name two cases where it wins:

  1. The entry for a key is written once and read many times, as in caches that only grow.
  2. Many goroutines read, write, and overwrite disjoint sets of keys.

In those cases it avoids lock contention on a single mutex, which matters on machines with many cores. Everywhere else a plain map with a mutex is usually faster, is type-safe, and supports len.

Go 1.24 reimplemented sync.Map on a concurrent hash-trie (internal/sync.HashTrieMap). That improved performance for mixed workloads and for disjoint writes. The old version used a read-only map plus a dirty map with promotion.

var cache sync.Map // zero value ready; do not copy after use

func getUser(id int) *User {
	if v, ok := cache.Load(id); ok {
		return v.(*User) // type assertion: no generics
	}
	u := loadUser(id)
	actual, _ := cache.LoadOrStore(id, u) // atomic; losers get the winner's value
	return actual.(*User)
}

// Other APIs: Store, Delete, LoadAndDelete, Swap, CompareAndSwap, CompareAndDelete,
// Range(func(k, v any) bool), Clear (Go 1.23)

Downsides:

  • It uses any, so every access needs a type assertion. Wrap it in a small generic type to get type safety.
  • There is no Len().
  • Range is not a consistent snapshot.
  • Values boxed in interfaces can cause extra allocations.
  • LoadOrStore still runs the costly loadUser on every racing goroutine. Use golang.org/x/sync/singleflight to deduplicate the work.

More on Arrays, Slices, Maps & Strings

All 37 Arrays, Slices, Maps & Strings questions