模拟了RWMutex的源代码,代码都有注释,伪代码,应该看起来好懂一点 其中忽略了很多竞争相关的代码,那个不是本文想讲的重点,所以忽略了,大家可以结合着去看源代码
package main
import (
"runtime"
"sync"
"sync/atomic"
)
type RWLock struct {
w sync.Mutex // 写锁
readerSem uint32 // 读锁信号量
writerSem uint32 // 写锁信号量
readerCount atomic.Int32 // 读锁的总数量
readerWait atomic.Int32 // 写锁获取之前统计的前面的读锁数量
}
const (
rwMaxReaders = 1 << 30 // 一个很大的数
)
// RLock 读锁之间不阻塞,但是读写锁阻塞
func (rw *RWLock) RLock() {
// 加读锁的时候,如果数量小于0,那说明有写锁了,因为写锁会加一个很大的负数 -rwMaxReaders
// 有写锁,那读就要阻塞了
if rw.readerCount.Add(1) < 0 {
// 模拟阻塞的方法,信号量是读信号量,后面写释放锁的时候要制定读信号量
runtime.runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)
}
return
}
// RUnlock 读释放锁
func (rw *RWLock) RUnlock() {
// 如果释放读锁之后,读数量是小于0,那说明可能有写锁在等着呢
if r := rw.readerCount.Add(-1); r < 0 {
rw.rUnlockSlow(r)
}
}
func (rw *RWLock) rUnlockSlow(r int32) {
// 如果+1后等于0,则r=-1, 这个-1刚好是前面RUnlock中-1得来的,所以相当于解锁了一个没有锁的情况
// 如果r+1 等于写锁赋值的最大负数,那说明是有写锁导致的<0,而没有读锁
if r+1 == 0 || r+1 == -rwMaxReaders {
panic("sync: RUnlock of unlocked RWMutex")
}
// 等最后一个读释放锁后,唤醒写
// 这个readerWait 刚好是写锁之前所有的正在读的数量,等写锁之前所有的读释放之后,就该到写锁了
// 按锁的前后顺序来的,这个写法太妙了
if rw.readerWait.Add(-1) == 0 {
runtime.runtime_Semrelease(&rw.writerSem, false, 1)
}
}
func (rw *RWLock) Lock() {
// 先占用写锁
rw.w.Lock()
// 给readerCount赋值一个很大的负数,表示有写锁,同时又获取当前正在读的数量
// 这种写法真的是大佬才能想到的啊
r := rw.readerCount.Add(-rwMaxReaders) + rwMaxReaders
// 有正在的读,那就把当前读的数量记录到readerWait,然后阻塞
// 等后续readerWait里的都释放之后,唤醒写writerSem
if r != 0 && rw.readerWait.Add(r) != 0 {
runtime.runtime_SemacquireRWMutex(&rw.writerSem, false, 0)
}
}
func (rw *RWLock) Unlock() {
// 1、标记后续的读锁,没有写锁了
// 2、而且这个r表示的数量正是写锁加上之后的被阻塞住的读的数量,太精妙了
r := rw.readerCount.Add(rwMaxReaders)
// 判断有没有写锁,如果有写锁,readerCount会赋值-rwMaxReaders
// 所以加上rwMaxReaders不可能大于等于它自己,只有一种可能,那就是没有写锁才成立
if r >= rwMaxReaders {
panic("没有写锁,你在释放什么")
}
// 到这,就要一个个唤醒之前阻塞住的读锁了
for i := 0; i < int(r); r++ {
runtime.runtime_Semrelease(&rw.readerSem, false, 0)
}
// 不要忘记释放些锁,给其他写锁一点机会
rw.w.Unlock()
}