大家好,我是小道哥。
上一篇为大家介绍了Go中的sync.Mutex互斥锁的数据结构及实现原理,今天继续为大家介绍另外一种常用的锁,那就是sync.RWMutex读写锁。
读写锁的使用
读写互斥锁sync.RWMutex不限制对资源的并发读,但是读写,写写操作无法并行执行。
读写锁一共有四个函数:
- RLock(): 申请读锁
- RUnlock(): 解除读锁
- Lock(): 申请写锁
- Unlock(): 解除写锁
实现原理
sync.RWMutex的数据结构
Go中sync.RWMutext的结构体为
type RWMutex struct {
w Mutex // 复用互斥锁
writerSem uint32 // 写锁监听读锁释放的信号量
readerSem uint32 // 读锁监听写锁释放的信号量
readerCount int32 // 当前正在执行读操作的数量
readerWait int32 // 当写操作被阻塞时,需要等待读操作完成的个数
}
接下来,我们结合底层数据结构来分析读写锁是如何实现对资源并发读写的控制的。
- 读操作如何防止并发读写问题的?
- RLock(): 申请读锁,每次执行此函数后,会对readerCount++,此时当有写操作执行Lock()时会判断readerCount>0,就会阻塞。
- RUnLock(): 解除读锁,执行readerCount–,释放信号量唤醒等待写操作的goroutine。
- 写操作如何防止并发读写、并发写写问题?
- Lock(): 申请写锁,获取互斥锁,此时会阻塞其他的写操作。并将readerCount 置为 -1,当有读操作进来,发现readerCount = -1, 即知道有写操作在进行,阻塞。
- Unlock(): 解除写锁,会先通知所有阻塞的读操作goroutine,然后才会释放持有的互斥锁。
-
为什么写操作不会被饿死?
这时候张三道友先跳出来问了一句,什么是写操作被饿死?
这是由于写操作要等待读操作结束后才可以获得锁,而写操作在等待期间可能还有新的读操作持续到来,如果写操作等待所有读操作结束,很可能会一直阻塞,这种现象称之为写操作被饿死。
通过RWMutex结构体中的readerWait属性可完美解决这个问题。
当写操作到来时,会把RWMutex.readerCount值拷贝到RWMutex.readerWait中,用于标记排在写操作前面的读者个数。
前面的读操作结束后,除了会递减RWMutex.readerCount,还会递减RWMutex.readerWait值,当RWMutex.readerWait值变为0时唤醒写操作。
面试点总结
- sync.RWMutex读写锁的数据结构?
- 读写锁如何控制资源并发读写?
- 连续读操作下,写操作如何保证不被饿死?
后语
如果大家对本文提到的面试技术点有任何问题,都可以在评论区进行回复哈,我们共同学习,一起进步!
关注公众号[简道编程],每天一个后端技术面试点