A common mistake is unlocking an RWMutex after assigning a shared map to a local variable, then iterating after unlock. Map assignment copies only the map header and still aliases the same runtime data, so a concurrent write races with iteration; slices behave similarly through shared backing arrays. section 9.10
Choose the critical-section boundary according to the work performed:
- Protect the entire read operation with RLock and defer RUnlock when iteration is short. section 9.10
- Copy every map entry while holding RLock, unlock, and perform expensive processing on the independent copy. section 9.10
- Validate inputs before acquiring a write lock when validation does not require protected state; this reduces lock scope and can avoid recursive locking. section 9.8.2
Formatting while holding the write lock
UpdateAge can lock the Customer, discover a negative age, and format an error containing %v and the receiver. Formatting calls Customer.String, which attempts RLock while UpdateAge already holds the write lock; the call therefore deadlocks. Validate age first, or format only unlocked fields such as the customer ID. section 9.8.2
func (c *Cache) AverageBalance() float64 {
c.mu.RLock()
balances := c.balances #1
c.mu.RUnlock()
sum := 0.
for _, balance := range balances { #2
sum += balance
}
return sum / float64(len(balances))
}
Inspect the aliasing pattern that leaves map iteration outside the read lock. section 9.10
func (c *Cache) AverageBalance() float64 {
c.mu.RLock()
defer c.mu.RUnlock() #1
sum := 0.
for _, balance := range c.balances {
sum += balance
}
return sum / float64(len(c.balances))
}
Compare the minimal whole-operation lock for inexpensive iteration. section 9.10



