参考广大网友的文章及本人浅薄的理解,如有错误希望指正,感谢 !
前提知识储备:互斥信号量pv操作。
type RWMutex struct {
w Mutex // held if there are pending writers
writerSem uint32 // semaphore for writers to wait for completing readers
readerSem uint32 // semaphore for readers to wait for completing writers
readerCount int32 // number of pending readers
readerWait int32 // number of departing readers
}
writerSem 写入操作的信号量,所有读操作完成并发送该信号量给正在等待写的操作。
也就是说需要写的时候如果有其他用户正在读数据,此时写就需要阻塞等待所有读完成,
最后一个完成读的操作会发送这个信号量给正在等待写的操作,此时写操作取消阻塞开始写文件
readerSem 读操作的信号量。读操作结束之后发送该信号,所有阻塞等待读的操作都可以获取该信号量并开始读文件。
readerCount 当前读操作的个数。读操作开始 readerCount++,结束 readerCount--
readerWait 当前写入操作需要等待读操作解锁的个数
RLock(获取读锁)
func (rw *RWMutex) RLock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
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))
}
}
读锁很简单,比较容易理解。
每当有一个读操作来的时候就将readerCount + 1,加1之后如果小于0,那么就要阻塞等待可读信号量 readerSem 的到来,至于为什么小于0时会阻塞等待readerSem信号量,在下一个函数Lock就知道了。
Lock(获取写锁)
func (rw *RWMutex) Lock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
// First, resolve competition with other writers.
rw.w.Lock()
// Announce to readers there is a pending writer.
r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
// Wait for active readers.
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))
}
}
每当有一个写操作来的时候就立刻 readerCount - rwmutexMaxReaders,让readerCount减去一个很大的数,然后在RLock中 给readerCount + 1 的时候 readerCount就为负数,这样做就可以阻止在写操作之后来的读操作读取数据,因为当我写操作正在修改数据,还没完成的时候就读数据的话这个时候读操作拿到的很可能是脏数据(这点类似于mysql中的事务,脏读) 。然后可以看到 r 就是原来的 readerCount,如果此时 r != 0 即当前有正在读的操作,并且readerWait + readerCount 需要等待读锁解锁的个数不为0 (即需要readerWait + readerCount个读操作全部释放读锁),那么写操作就会进入阻塞状态,等待读锁全部释放并且最后一个读操作给当前阻塞的写操作发送 writerSem 写就绪信号量。
一开始获取写锁的时候 readerWait 为0, 直接加上 readerCount,此时两者相等,之后每次获取写锁的时候都会把新增加的读操作readerCount 加入到 readerWait里去。
RUnlock(释放读锁)
func (rw *RWMutex) RUnlock() {
if race.Enabled {
_ = rw.w.state
race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
race.Disable()
}
if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
// Outlined slow-path to allow the fast-path to be inlined
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)
}
}
if race.Enabled {
race.Enable()
}
}
释放读锁不光是将之前+1的readerCount,-1就完事了,其实还有一些操作需要注意。
第一种情况:如果 readerCount - 1 之后为 -1 ,也就是 r+1==0的时候,说明没有获取读锁就想去释放读锁,于是异常了;
第二种情况:r+1 == -rwmutexMaxReaders 时证明获取了写锁而去释放了读锁,导致异常,推导过程 --> 由函数Lock可知在没有读操作来的时候,readerCount =0,获取写锁时 readerCount = readerCount - rwmutexMaxReaders,然后在当前函数RUnlock中 r = readerCount - rwmutexMaxReaders - 1, r + 1 = readerCount - rwmutexMaxReaders,将readerCount =0 带入,得到 r+1 = - rwmutexMaxReaders。
除去上面两个可能的异常情况,剩下的就是 r < 0 的情况,那么证明确实有协程正在想要获取写锁,因为在Lock函数中尝试获取写锁时会减去一个很大的数rwmutexMaxReaders,所以此时我们需要操作 readerWait 令其减1,每个读操作结束后都减1,当readerWait减到0的时候就证明没有人正在持有写锁了,就通过信号量writerSem的变化告知刚才等待的协程(想要获取写锁的协程):你可以进行获取了写锁进行修改操作。
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.
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()
}
}
写锁释放需要恢复readerCount,还记得上锁的时候减了一个很大的数,这个时候要加回来了。当然加完之后如果>=rwmutexMaxReaders本身,那么就是没有获取写锁的时候就开始想着释放写锁了。
然后for循环就是为了通知所有在我们RLock函数中看到的,当有因为持有写锁所以等待的那些协程,通过信号量readerSem告诉他们可以动了。
最后别忘记还有一个互斥锁需要释放,让别的协程也可以开始抢写锁了。