RWMutex Implementation Explained
RWMutex Implementation Explained
Write Lock
func (rw *RWMutex) Lock() {
// Race detection
if race.Enabled {
_ = rw.w.state
race.Disable()
}
// Use mutex lock
rw.w.Lock()
// Mark that write lock is acquired
r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
// Wait for readers to finish, writer blocks
if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
runtime_Semacquire(&rw.writerSem)
}
// Race detection
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
race.Acquire(unsafe.Pointer(&rw.writerSem))
}
}The write lock does several things. First it grabs the underlying mutex. Then it marks that a write lock exists by subtracting a large number from readerCount. This makes readerCount go negative. Any goroutines trying to get read locks will now see this negative value and block.
The code then waits until all current readers finish. It counts waiting readers with readerWait and uses a semaphore to sleep until it can proceed.
Read Lock
func (rw *RWMutex) RLock() {
// Race detection
if race.Enabled {
_ = rw.w.state
race.Disable()
}
// Each time a goroutine gets a read lock, increment readerCount
// If write lock is held, readerCount is between -rwmutexMaxReaders and 0, so block the goroutine
// If no write lock, readerCount > 0, acquire read lock without blocking
// Use readerCount to check if readers and writers conflict
if atomic.AddInt32(&rw.readerCount, 1) < 0 {
// Put goroutine at end of queue, block it
runtime_Semacquire(&rw.readerSem)
}
// Race detection
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
}
}The read lock is simpler. It increments readerCount atomically. If readerCount goes negative, that means a writer holds the lock. The reader blocks on the semaphore.
If readerCount stays positive, the read lock succeeds immediately. Multiple readers can run in parallel since they all just increment the counter.