golang源码学习之sync.RWMutex

419 阅读1分钟

一、核心数据结构

type RWMutex struct {
	w           Mutex  // held if there are pending writers
	writerSem   uint32 // semaphore for writers to wait for completing readers   唤醒write的信号量
	readerSem   uint32 // semaphore for readers to wait for completing writers 唤醒read的信号量
	readerCount int32  // number of pending readers 读锁计数,write lock的时候,会设置成一个很大的负数来表示标识,阻塞后面的read
	readerWait  int32  // number of departing readers 解除lock的reader数量(负数)
}

源码注释提及到了,RWMutex在第一次使用之后不能被copy,禁止递归使用read lock.数据结构表明了设计思想,依赖Mutex、信号量、reader计数来实现读写互斥

二、核心函数

1、RLock
func (rw *RWMutex) RLock() {
	if race.Enabled {
		_ = rw.w.state
		race.Disable()
	}
	// 说明readerCount是个负数,应为write lock的时候会加上一个很大的负数来声明,有write lock来了,后面来的read都要阻塞,直到write unlock
	if atomic.AddInt32(&rw.readerCount, 1) < 0 {
		// A writer is pending, wait for it.
		runtime_SemacquireMutex(&rw.readerSem, false, 0)
	}
	if race.Enabled {
		race.Enable()
		race.Acquire(unsafe.Pointer(&rw.readerSem))
	}
}
2、RUnlock
func (rw *RWMutex) RUnlock() {
	if race.Enabled {
		_ = rw.w.state
		race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
		race.Disable()
	}
	// r<0,说明有write在阻塞,需要唤醒
	// 只存在read的情况下,r最小值应该是0(除非重复调用runlock)
	if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
		// Outlined slow-path to allow the fast-path to be inlined
		rw.rUnlockSlow(r)
	}
	if race.Enabled {
		race.Enable()
	}
}

func (rw *RWMutex) rUnlockSlow(r int32) {
	// 判断是否是重复解锁,重复解锁直接panic
	if r+1 == 0 || r+1 == -rwmutexMaxReaders {
		race.Enable()
		throw("sync: RUnlock of unlocked RWMutex")
	}
	// A writer is pending.
	if atomic.AddInt32(&rw.readerWait, -1) == 0 {
		// The last reader unblocks the writer.
		runtime_Semrelease(&rw.writerSem, false, 1)
	}
}
3、Lock
func (rw *RWMutex) Lock() {
	if race.Enabled {
		_ = rw.w.state
		race.Disable()
	}
	// First, resolve competition with other writers.
	// 多个write锁,需要依次等待
	rw.w.Lock()
	// Announce to readers there is a pending writer.
	// 声明有write lock请求
	r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
	// Wait for active readers.
	// r!=0 代表还有已经有reader hold lock
	// 解除lock的数量不等于持有lock的read,需要阻塞
	if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
		runtime_SemacquireMutex(&rw.writerSem, false, 0)
	}
	if race.Enabled {
		race.Enable()
		race.Acquire(unsafe.Pointer(&rw.readerSem))
		race.Acquire(unsafe.Pointer(&rw.writerSem))
	}
}
4、Unlock
func (rw *RWMutex) Unlock() {
	if race.Enabled {
		_ = rw.w.state
		race.Release(unsafe.Pointer(&rw.readerSem))
		race.Disable()
	}

	// Announce to readers there is no active writer.
	r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
	if r >= rwmutexMaxReaders {
		race.Enable()
		throw("sync: Unlock of unlocked RWMutex")
	}
	// Unblock blocked readers, if any.
	// 解除所有被阻塞的read lock
	for i := 0; i < int(r); i++ {
		runtime_Semrelease(&rw.readerSem, false, 0)
	}
	// Allow other writers to proceed.
	rw.w.Unlock()
	if race.Enabled {
		race.Enable()
	}
}

三、总结

关于RWMutex可以分成四种场景RR、WW、RW、WR

1、RR场景

在两次读的时候对readerCount加1,不会小于0,所以不会阻塞 image.png

2、WW场景

首先抢占mutex排队进入,进入之后,将readerCount加上一个-rwmutexMaxReaders来申声明有write操作,然后判断readerCount是否==0,还有已经在write来之后已经解除lock的read和目前还持有lock的read数量是否一致。由于没有read lock所以不会在信号量上等待。所以两个WW只通过mutex来互斥

3、RW场景

read获取lock对readerCount加1,write获取mutex,然后将readerCount加上一个-rwmutexMaxReaders来声明有write操作,判断readerCount原始的值不等于0,并且在write来之后解除read lock的数量不等于readerCount原始的值,write在writerSem信号量上阻塞,等待unlock read唤醒

r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
		runtime_SemacquireMutex(&rw.writerSem, false, 0)
	}

之所以需要判断atomic.AddInt32(&rw.readerWait, r) != 0是应为在计算r到判断r!=0之间,可能read lock就已经释放了,此时又去阻塞在writerSem信号量上,则会死锁

4、WR场景

write获取mutex,然后将readerCount加上一个-rwmutexMaxReaders来声明有write操作,判断readerCount原始的值等于0,不阻塞在writerSem信号量上,read来之后,对readerCount加1,发现结果小于0,就阻塞在readerSem的信号量上,等待unlock write唤醒

综上,采用了原子操作,所以是能够保证执行顺序,在并发的时候能够出现顺序

// Happens-before relationships are indicated to the race detector via:
// - Unlock  -> Lock:  mutex
// - Unlock  -> RLock: readerSem
// - RUnlock -> Lock:  writerSem