go sync.RWMutex
TL;DR
所有读写锁的实现都满足:读不互斥,读写互斥,写写互斥。在此基础上,可能会有不同的实现,导致读写的优先级不同,以及可重入与否
sync.RWMutex的可重入性:在有写者等待时,活动中的读者是不可重入的;活动中的写者不可重入。以上情况如果重入,会死锁。(可以发现go目前并没提供任何可重入的锁,似乎也不提倡对资源重复锁定,更倾向于资源传递。当然如果你传的是指针,那么还是要注意所指的数据会否被并发修改)
sync.RWMutex的读写优先级:当有一个或多个写者等待时,后来的读者会等待一个写者执行完,才能获取读锁,剩余的写者又会等待这批读者执行完,才能获取写锁
例如以下加锁序列:
r1、r2、w1、r3、r4、w2 =》r1、r2、w1、r2、r3、w2,即:r3r4等待w1加锁和解锁,w2等待r3r4加锁和解锁
r1、r2、w1、w2、r3、r4 =》r1、r2、w1、r2、r3、w2,即:r3r4等待w1加锁和解锁,w2等待r3r4加锁和解锁(w2等待w1及其后面所有读者)
源码
type RWMutex struct {
w Mutex // 只能有一个写者锁定,所以用Mutex是合适的
writerSem uint32 // 让写者睡眠和醒来的信号量
readerSem uint32 // 让读者睡眠和醒来的信号量
readerCount int32 // 总读者数(含活动中的和等待中的,当此数为负,说明有写者(活动中或等待中),需要加rwmutexMaxReaders得知)
readerWait int32 // 写者出现时,前面活动中的读者数,需要等待它们解锁才能唤醒写者
}
func (rw *RWMutex) Lock() {
// 获取锁,这样其它写者就进入不了,保证后来的写者等待前面的写者执行完才能加写锁
rw.w.Lock()
// 将readerCount变成负数(使后来的读者都要先等待本写者,见RLock)
// 再加回rwmutexMaxReaders,得原读者数
r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
// 如果原读者数为零(第一条件不成立),则相当于获取到写锁(即便在上一步之后有人调用RLock,也会因为readerCount为负而睡眠)
// 如果原读者数不为零(正数),就将读者数加到readerWait,以示等待readerWait个读者
// 当然,在上一步与这一步之间,readerWait有可能已经变成-r(读者都已离开,见rUnlockSlow),
// 使AddInt32(readerWait)恢复至零(条件二不成立),则相当于取得写锁
// 也当然,在上一步与这一步之间,可能会有更多读者想进来,但它们都会等待本写者先执行(见RLock)
// 如果AddInt32(readerWait)是正数,则睡眠
if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
runtime_SemacquireMutex(&rw.writerSem, false, 0)
}
}
func (rw *RWMutex) Unlock() {
// 将readerCount恢复回正数
r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
if r >= rwmutexMaxReaders {
throw("sync: Unlock of unlocked RWMutex")
}
// 唤醒所有等待中的读者
for i := 0; i < int(r); i++ {
runtime_Semrelease(&rw.readerSem, false, 0)
}
rw.w.Unlock()
}
func (rw *RWMutex) RLock() {
// 读者加一
// 如果readerCount已被写者改为负数(有写者执行中或等待中)
// 则本读者也等待(优先写者),相当于把后来源源不断的读者截住
if atomic.AddInt32(&rw.readerCount, 1) < 0 {
runtime_SemacquireMutex(&rw.readerSem, false, 0)
}
}
func (rw *RWMutex) RUnlock() {
// 读者数减一
// 如果readerCount已为负数(有个写者等待中),则尝试唤醒它
if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
rw.rUnlockSlow(r)
}
}
func (rw *RWMutex) rUnlockSlow(r int32) {
if r+1 == 0 || r+1 == -rwmutexMaxReaders {
throw("sync: RUnlock of unlocked RWMutex")
}
// 只要写者将readerCount变成负数,那么写者等待的读者数(readerWait)就不会增加,后来的读者都会等写者先执行
// 所以,只要之前的读者(readerWait)都逐个减一,直至到零,就可以唤醒写者让其执行
if atomic.AddInt32(&rw.readerWait, -1) == 0 {
runtime_Semrelease(&rw.writerSem, false, 1)
}
}