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)
    }
}