目录前提:读写锁互斥性写锁上锁场景:写锁释放锁场景:读锁上锁场景:读锁释放锁场景:在简略的说之前,首先要对RW锁的结构有一个大致的了解 type RWMutex struct {
在简略的说之前,首先要对RW锁的结构有一个大致的了解
type RWMutex struct {
w Mutex // 写锁互斥锁,只锁写锁,和读锁无关
writerSem uint32 // sema锁--用于“写协程”排队等待
readerSem uint32 // sema锁--用于“读协程”排队等待
readerCount int32 // 读锁的计数器
readerWait int32 // 等待读锁释放的数量
}
这里要额外说一句,writerSem和readerSem底层都是semaRoot,这个结构体有兴趣可以了解下,他的用法有点类似于一个简版的channel,很多地方把他的初始值设置为0,使得所有想获取该sema锁的协程都排队等待,也就是说初始值为0的sema锁,他本身起到的作用是成为一个协程等待队列,就像没有缓冲区的channel一样。
好现在进入正题。本文是为了在面试中能快速口述RW锁,并非为了完整解答RW锁的机制。
readerCount这个参数非常重要
一个很重要的参数:const rwmutexMaxReaders = 1 << 30 ,rwmutexMaxReaders 非常大,意思是最多能有rwmutexMaxReaders(1 << 30 十进制为 4294967296)个协程同时持有读锁。
首先分析写锁,因为读锁的很多操作是根据写锁来的,如果一上来就说读锁,很多东西没法串起来
func (rw *RWMutex) Lock() {
// race.Enabled是官方的一些测试,性能检测的东西,无需关心,这个只在编译阶段才能启用
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))
}
}
1.获取写锁--没有读锁等待
2.获取写锁--有读锁等待
3.获取写锁--前面已经有写锁了
后面的写协程也调用 rw.w.Lock() 进行加锁,因为前面有写锁已经获取了w,所以后续的写协程会因为获取不到w,而进入到w的sema队列里面,w是一个mutex的锁,mutex锁里是一个sema锁,sema锁因为没有设置初始值,所以退化为一个队列,而获取不到w锁的就会直接被阻塞在w的sema队列里,从而无法进行接下来的操作
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()
}
}
1.释放写锁--后面【没有】读锁等待
2.释放写锁--后面【有】读锁等待
3.释放写锁--后面有【写锁】等待
func (rw *RWMutex) RLock() {
// race.Enabled都是测试用的代码,在阅读源码的时候可以跳过
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))
}
}
1.获取读锁--此时没有写锁.
最简单的场景,协程对rw.readerCount进行原子操作加一,如果得到的结果为正数,说明获取读锁成功。
2.获取读锁--前方已经有写锁抢占了该锁
3.获取读锁--前方有写锁抢已经被抢占,后方有写锁等待
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
rw.rUnlockSlow(r)
}
if race.Enabled {
race.Enable()
}
}
1.释放读锁--后方没有写锁等待
2.释放读锁--后方有写锁等待
func (rw *RWMutex) rUnlockSlow(r int32) {
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)
}
}
这里是有个前提的,上面提到(详见上面的获取写锁的场景1),如果写协程进来想加写锁,需要把它需要等待的读锁数量从readerCount里赋值给readerWait。当它等待的读锁释放后,就需要用rUnlockSlow方法对readerWait进行减1,如果readWait == 0 ,说明这是最后一个需要等待的读锁也释放了,释放后就通知该写锁可以被唤醒了,锁给你了。
到此这篇关于go的sync.RWMutex锁的文章就介绍到这了,更多相关go的sync.RWMutex锁内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
--结束END--
本文标题: 一文掌握go的sync.RWMutex锁
本文链接: https://lsjlt.com/news/199168.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-04-05
2024-04-05
2024-04-05
2024-04-04
2024-04-05
2024-04-05
2024-04-05
2024-04-05
2024-04-04
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0