Introduction
When reads vastly outnumber writes, sync.RWMutex provides better throughput than a regular Mutex by allowing multiple concurrent readers while still giving writers exclusive access.
Key Concepts
- sync.RWMutex: A reader/writer mutual exclusion lock. Multiple goroutines can hold the read lock simultaneously, but only one can hold the write lock.
- RLock/RUnlock: Acquire and release a shared read lock.
- Lock/Unlock: Acquire and release an exclusive write lock.
Real World Context
A configuration cache read thousands of times per second but updated only on config file changes is a perfect use case. Using Mutex would serialize all reads unnecessarily. RWMutex lets all readers proceed in parallel, only blocking when a writer needs to update the config.
Deep Dive
When reads are much more frequent than writes, RWMutex provides better performance:
- Multiple readers can hold the lock simultaneously.
- Writers get exclusive access — no readers or other writers can hold the lock.
govar rwmu sync.RWMutex var data map[string]string func read(key string) string { rwmu.RLock() defer rwmu.RUnlock() return data[key] } func write(key, value string) { rwmu.Lock() defer rwmu.Unlock() data[key] = value }
When to Use
- Many concurrent readers, few writers.
- Read operations significantly outnumber writes.
- Read operations do not need to see immediately updated values.
Writer Priority
Go's RWMutex gives pending writers priority over new readers. Once a writer calls Lock(), new RLock() calls block until the writer finishes. This prevents writer starvation but means a burst of write requests can temporarily block readers.
Common Pitfalls
- Upgrading RLock to Lock — You cannot upgrade a read lock to a write lock. Calling
Lock()while holdingRLock()causes a deadlock. Release the read lock first. - Using RWMutex when writes are frequent — If writes are common, the overhead of RWMutex exceeds that of a plain Mutex. Profile before choosing.
Best Practices
- Profile before switching from Mutex to RWMutex — The benefit depends on the read/write ratio. For write-heavy workloads, plain Mutex is simpler and often faster.
- Keep read-locked sections short — Long read-locked sections delay writers, increasing latency for updates.
Summary
RWMutexallows concurrent readers but exclusive writers.- Go's implementation gives pending writers priority over new readers, preventing writer starvation.
- Never try to upgrade RLock to Lock — it causes deadlock.
- Profile to confirm the read/write ratio justifies RWMutex over Mutex.
Code Examples
type SafeCache struct {
mu sync.RWMutex
items map[string]Item
}
func (c *SafeCache) Get(key string) (Item, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
item, ok := c.items[key]
return item, ok
}
func (c *SafeCache) Set(key string, item Item) {
c.mu.Lock()
defer c.mu.Unlock()
c.items[key] = item
}