tezvyn:

Go's sync.Map: A Specialized Concurrent Map

AI-drafted, machine-checkedSource: pkg.go.devadvanced

Go's `sync.Map` is a concurrent map optimized for keys written once and read many times. It's ideal for long-lived caches, but it's not a generic replacement for a map with a mutex. The footgun is using it for frequent writes, which can be slower.

WHY IT EXISTS: A standard Go map is not safe for concurrent access. The naive solution is to wrap every access with a single sync.Mutex or RWMutex, but this can cause lock contention and hurt performance when many goroutines are reading and writing. sync.Map was created to provide a more performant alternative for a very specific access pattern.

THE MENTAL MODEL: Think of sync.Map as having two maps: a main, read-only map that many goroutines can access without locks, and a secondary, "dirty" map for new writes that requires a lock. When you read a key, it tries the lock-free map first. This makes reads for existing keys extremely fast. Only if the key is missing does it fall back to the slower, locked path.

HOW IT WORKS: Internally, sync.Map uses an atomic pointer to a read-only data structure for most Load calls. This allows multiple goroutines to read concurrently without any mutexes. When a key is written via Store or LoadOrStore, it's added to a separate "dirty" map that requires a lock. If a Load misses the read-only map, it will check the dirty map under a lock. Periodically, the dirty map is promoted to become the new read-only map, amortizing the cost of locking over many reads.

WHEN TO USE IT: Use sync.Map when your map's keys are stable and the primary workload is reads from many concurrent goroutines. The canonical use case is a cache that is populated once (or infrequently) and then read from heavily. The keys are written once and live for the lifetime of the map.

WHEN NOT TO USE IT: Do not use sync.Map as a general-purpose replacement for a map with a mutex. If your workload involves many writes, updates, or deletions, the overhead of managing the two internal maps can make sync.Map slower than a simple map protected by a sync.RWMutex. Also, it does not have a Len() method to get its size.

ONE CANONICAL EXAMPLE: A service registry in a microservices architecture. When a service starts, it registers its address in a sync.Map. This is a one-time write. Many other services will then concurrently read from this map to discover the address. Since the set of services is relatively stable, the workload is almost all reads, making sync.Map a perfect fit.

Read the original → pkg.go.dev

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.