How to Use sync.RWMutex in Go for Safe Concurrent Access
In Go, use
sync.RWMutex to protect shared data by allowing multiple readers or one writer at a time. Use RLock() and RUnlock() for reading, and Lock() and Unlock() for writing to ensure safe concurrent access.Syntax
The sync.RWMutex type provides two kinds of locks: read locks and write locks.
- Lock(): Acquires a write lock. Only one goroutine can hold it, blocking others.
- Unlock(): Releases the write lock.
- RLock(): Acquires a read lock. Multiple goroutines can hold it simultaneously.
- RUnlock(): Releases the read lock.
Use write locks when modifying data, and read locks when only reading data.
go
var mu sync.RWMutex // For writing: mu.Lock() // modify shared data mu.Unlock() // For reading: mu.RLock() // read shared data mu.RUnlock()
Example
This example shows a shared counter accessed by multiple goroutines. Readers use RLock() to read the value concurrently, while writers use Lock() to update it exclusively.
go
package main import ( "fmt" "sync" "time" ) func main() { var mu sync.RWMutex counter := 0 wg := sync.WaitGroup{} // Writer goroutine wg.Add(1) go func() { defer wg.Done() for i := 0; i < 3; i++ { mu.Lock() counter++ fmt.Println("Writer incremented counter to", counter) mu.Unlock() time.Sleep(100 * time.Millisecond) } }() // Reader goroutines for i := 0; i < 3; i++ { wg.Add(1) go func(id int) { defer wg.Done() for j := 0; j < 3; j++ { mu.RLock() fmt.Printf("Reader %d reads counter: %d\n", id, counter) mu.RUnlock() time.Sleep(50 * time.Millisecond) } }(i + 1) } wg.Wait() }
Output
Writer incremented counter to 1
Reader 1 reads counter: 1
Reader 2 reads counter: 1
Reader 3 reads counter: 1
Reader 1 reads counter: 1
Writer incremented counter to 2
Reader 2 reads counter: 2
Reader 3 reads counter: 2
Reader 1 reads counter: 2
Reader 2 reads counter: 2
Writer incremented counter to 3
Reader 3 reads counter: 3
Reader 1 reads counter: 3
Reader 2 reads counter: 3
Reader 3 reads counter: 3
Common Pitfalls
Common mistakes when using sync.RWMutex include:
- Forgetting to unlock after locking, causing deadlocks.
- Using
Lock()when only reading, which reduces concurrency. - Using
RLock()when writing, which can cause data races. - Holding locks for too long, blocking other goroutines.
Always pair Lock() with Unlock() and RLock() with RUnlock() in the same function or scope.
go
package main import ( "sync" ) func main() { var mu sync.RWMutex shared := 0 // Wrong: Using RLock for writing (causes data race) mu.RLock() shared = 1 // unsafe write mu.RUnlock() // Correct way: mu.Lock() shared = 1 mu.Unlock() }
Quick Reference
Remember these tips when using sync.RWMutex:
- Use
RLock()/RUnlock()for reading to allow multiple readers. - Use
Lock()/Unlock()for writing to ensure exclusive access. - Never mix read and write locks incorrectly.
- Always unlock in the same function to avoid deadlocks.
- Keep lock scope as small as possible for better performance.
Key Takeaways
Use sync.RWMutex to allow multiple concurrent readers or one exclusive writer.
Always pair Lock with Unlock and RLock with RUnlock to avoid deadlocks.
Use RLock for reading and Lock for writing to protect shared data safely.
Avoid holding locks longer than necessary to keep your program responsive.
Incorrect use of locks can cause data races or deadlocks, so be careful.