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.