05| RWMutex:读写锁的实现原理及避坑指南
该思维导图由 AI 生成,仅供参考
- 深入了解
- 翻译
- 解释
- 总结
RWMutex是Go语言中用于解决读写锁问题的并发原语,通过区分读写操作,提高了并发读取的性能。文章介绍了RWMutex的使用场景、实现原理和避坑指南。RWMutex允许多个goroutine同时持有读锁,但只允许一个goroutine持有写锁,通过Lock/Unlock和RLock/RUnlock方法实现对共享资源的保护。在实际应用中,RWMutex适用于大量并发读、少量并发写的场景,能有效提升性能。文章还解释了RWMutex的实现原理,包括基于Mutex的设计方案和具体字段。总的来说,RWMutex是一个高效的并发原语,适用于读写操作频繁且性能要求较高的场景。 文章详细介绍了RWMutex的实现原理,包括RLock/RUnlock和Lock的具体实现方式。在RLock/RUnlock中,通过对readerCount字段的处理,实现了对reader和writer的优先级控制,保证了并发读取和写入的正确性。而在Lock方法中,通过内部的互斥锁保证了对writer的互斥访问。此外,文章还指出了RWMutex的三个常见踩坑点,如不可复制等,为读者提供了实际应用中需要注意的问题。 总的来说,本文通过深入浅出的方式介绍了RWMutex的概念和实现原理,适合读者快速了解并发编程中读写锁的使用和注意事项。文章还列举了一些流行的Go开发项目中使用RWMutex时出现的问题,以及在实际开发中需要注意的细节。通过这些案例,读者可以更好地理解RWMutex的使用和避坑方法,为并发编程提供了有益的参考。 总体而言,本文对RWMutex进行了全面而深入的介绍,既包括理论知识又有实际案例,适合读者快速了解并发编程中读写锁的使用和注意事项。
《Go 并发编程实战课》,新⼈⾸单¥59
全部留言(27)
- 最新
- 精选
- DigDeeply这门课的质量真高啊,看的酣畅淋漓👍
作者回复: 这是对作者很高的褒奖,谢谢。此留言应该置顶啊
2020-12-0428 - 那时刻请问老师一个关于select{}问题, func foo() { fmt.Println("in foo") } func goo() { var i int fmt.Println("in goo", i) } func main() { go goo() go foo() select {} } 这个代码会报all goroutines are asleep - deadlock,是不是select{}这种写法不推荐么?
作者回复: 现在版本的go运行时过于智能了,会把这个场景“误报”成死锁,其实我们的本意是让程序hang在这里。 可以改成sleep,waitgroup或者从命令行读取数据等的方式阻塞主goroutine
2020-10-238 - Varphp原谅我的小白 请教个问题 RWMutex是读写锁,Mutex就是锁吗?区别在于一个精确到读写 一个只能精确到协程对吗
作者回复: 第一个问题 对。第二个问题太过笼统所以没法回答,当然文章中已经介绍读写锁的功能,适合写少读多的场景
2021-01-161 - Davidfunc (rw *RWMutex) RLock() { if atomic.AddInt32(&rw.readerCount, 1) < 0 { // rw.readerCount是负值的时候,意味着此时有writer等待请求锁,因为writer优先级高,所以把后来的reader阻塞休眠 runtime_SemacquireMutex(&rw.readerSem, false, 0) } } 这个代码, rw.readerCount 为负数,表示有writer,正在等待,则reader要进行休眠。 func (rw *RWMutex) Lock() { // 首先解决其他writer竞争问题 rw.w.Lock() // 反转readerCount,告诉reader有writer竞争锁 r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders // 如果当前有reader持有锁,那么需要等待 if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 { runtime_SemacquireMutex(&rw.writerSem, false, 0) } } 这里是先获取到了锁,才去修改 rw.readerCount 的值,也就是说 每个writer 只有在获取到锁的情况下,才去把 rw.readerCount改成负数,而读锁是否休眠,也是根据这个值来判断。 func (rw *RWMutex) Unlock() { // 告诉reader没有活跃的writer了 r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders) // 唤醒阻塞的reader们 for i := 0; i < int(r); i++ { runtime_Semrelease(&rw.readerSem, false, 0) } // 释放内部的互斥锁 rw.w.Unlock() } 而这个地方 先去释放了 reader,再去释放阻塞的writer 假设一种情况,在lock的时候,先后有a,b两个writer 进来,发现还有其他的reader正在使用,那么a,b进行阻塞,当reader 使用完了后,唤醒了a,a获取到了锁,在a使用期间,也有多个reader 进来,进行的休眠。当a使用完成后,调用unlock方法,先是修改了rw.readerCount(根据rlock的方法,rw.readerCount是唯一判断,是否阻塞的条件,那么,当这个时候,有新的reader进来,就可以无条件使用了);再去唤醒被阻塞的reader(这个情况下,唤醒的reader 也可以进行无条件使用了),最后去释放锁,唤醒了b,b去获取锁的时候,发现有reader在使用,修改rw.readerCount的值(标示有等待的writer),然后进行休眠,当最后一个reader使用完成后,唤醒b,这个时候b才正在获取到了锁。 按照以上逻辑,多个writer的情况下,并没有造成reader出现饥饿状态,反而在释放写锁的那一刹那,新的reader 占了先机,这种情况怎么叫 Write-preferring 方案。我理解的Write-preferring 方案:是只有在没有writer的情况下,才轮到reader执行,多个writer的情况,是一个writer一个writer执行完,才会执行reader。我从代码中看出了不明白的地方,希望老师 帮忙解答一下
作者回复: 在readers已占有锁的情况下,后续只要有writer存在,会优先writer执行锁,后续的reader需要writer释放锁才可以获取
2021-01-0441 - 50%r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders 这句老师看我理解的对么。首先前一步反转操作是用原子操作实现的,此时其他的reader可能会改变readerCount字段的状态。虽然看起来加减同一个rwmutexMaxReaders看似结果相等,但在并发的场景下,其他reader读到的readerCount的状态为负值,表示有写锁的情况存在。
作者回复: 对
2020-11-071 - john-jy没人发现factorial退出条件是错误的吗?
作者回复: 👍🏻😁是的, 改成n=1
2023-06-07归属地:北京 - Traveller第二种死锁的场景有点隐蔽。我们知道,有活跃 reader 的时候,writer 会等待,如果我们在 reader 的读操作时调用 writer 的写操作(它会调用 Lock 方法),那么,这个 reader 和 writer 就会形成互相依赖的死锁状态。Reader 想等待 writer 完成后再释放锁,而 writer 需要这个 reader 释放锁之后,才能不阻塞地继续执行。这是一个读写锁常见的死锁场景。 这是什么意思?完全看不懂
作者回复: 在同一个goroutine中,先调用读锁,再调用写锁,会导致死锁
2023-04-27归属地:上海 - 极客酱酱鸟窝老师,麻烦问一下您这边使用的脑图工具是哪个,我正在学习这种用脑图梳理知识点的方法,手头的工具不太友好,手动狗头。
作者回复: processon
2022-04-19 - 8.13.3.27.30有一个问题、写错unlock逻辑中、解锁之前会唤醒所有等待读的锁、但是再锁的过程当中并可能会继续来读和写的操作、这些操作这里貌似并不能保证写锁的优先,因为写锁解锁的过程会把后来的读锁也唤醒、而这个时候后来的写锁(但是它先于后来的读锁操作锁)没有办法优先后来的读锁获取到锁,不知道理解是否正确,另外读写锁的实现看上去在极限情况下会导致写饥饿、当然也可能是读饥饿(理论上这种情况不应该使用读写锁)、请教老师我的理解是否正确,如果正确怎么解决写饥饿的问题
作者回复: 很好的发现,你说的对
2021-11-27 - 🐭第一个例子中,写操作在主携程里,为什么还要加锁
作者回复: 嗯,这个例子不太好,应该用两个writer更好理解
2021-08-06