当前位置:网站首页>golang 重要知识:RWMutex 读写锁分析
golang 重要知识:RWMutex 读写锁分析
2022-06-23 14:38:00 【yue_xin_tech】
摘要
在上一篇文章 golang 重要知识:mutex 里我们介绍了互斥锁 mutex 的相关原理实现。而且在 Go 里除了互斥锁外,还有读写锁 RWMutex,它主要用来实现读共享,写独占的功能。今天我们也顺便分析下读写锁,加深对 Go 锁的理解。
读写锁的实现原理
所谓的读写锁,其实就是针对下面的两种场景,对 Goroutine 之间的同步互斥进行控制:
- 多个 goroutine 一起占有读锁,互不影响,可以继续自己后面的逻辑代码。
- 写锁正在占有着,则后面的 goroutine 无论是要进行读锁占有,还是写锁占有,都将会被阻塞等待,直到当前的写锁释放。
弄清楚上面的场景需求后,实现就简单多了,关键就在于判断当前是否处于写锁状态即可,毕竟需要有阻塞等待的动作。
按照常规思路,我们一般会采用一个标识位来维护这个状态。然而,Go 官方却连这一步都省了。
利用了一个本来就得维护的读锁数量,在进行写锁占有时,使它变为负数。
后面有新进来的读写操作,只需要判断该值是否正负即可,负数则代表当前正在进行写锁占有,需要阻塞等待。
而在写锁占有结束后,该值又会恢复为正数,又可以进行新的读写操作了。
RWMutex 源码分析
接下来,我们到 src/runtime/rwmutex.go里具体分析下 RWMutex 的代码结构。
// rwmutex 是一个读写互斥的锁
// 将允许多个 goroutine 持有读锁,但写锁只会有一个持有
// rwmutex 使用了 sync.RWMutex 来辅助写锁互斥
type rwmutex struct {
rLock mutex // 用于保护设置 readers, readerPass, writer
readers muintptr // 休眠等待的 goroutine 读锁队列,等到写锁占有结束后将对应被唤起。
readerPass uint32 // 读锁队列需要跳过的 goroutine 数量,当在写锁结束后会唤起读锁队列里的 goroutine,但有的可能已不在队列里了,这部分需跳过。
wLock mutex // 用于 writer 之间的互斥锁
writer muintptr // 等待读完成的 writer
readerCount uint32 // 正在执行读操作的 goroutine数量
readerWait uint32 // 等待读锁释放的数量。当写锁占有后,前面还有部分读锁在继续着,需要等它们释放才能继续进行。
}
RWMutex 的 Lock() 分析
func (rw *rwmutex) Lock() {
// 用于多个写锁之间的的竞争
lock(&rw.wLock)
m := getg().m
// 将读锁数量 readerCount 置为负数,用于判断当前是否处于写锁占有状态,
// rw.readerCount < 0 则表示当前正在进行写锁占有.
r := int32(atomic.Xadd(&rw.readerCount, -rwmutexMaxReaders)) + rwmutexMaxReaders
// 前面还有读锁在进行着,需要等待释放完才能继续
lock(&rw.rLock)
if r != 0 && atomic.Xadd(&rw.readerWait, r) != 0 {
systemstack(func() {
rw.writer.set(m)
unlock(&rw.rLock)
notesleep(&m.park)
noteclear(&m.park)
})
} else {
unlock(&rw.rLock)
}
}
RWMutex 的 RLock() 分析
func (rw *rwmutex) Rlock() {
acquirem()
if int32(atomic.Xadd(&rw.readerCount, 1)) < 0 {
// 读锁数量 readerCount + 1 后小于 0,表示当前正被写锁占有,
// 等待写锁释放
systemstack(func() {
lock(&rw.rLock)
if rw.readerPass > 0 {
rw.readerPass -= 1
unlock(&rw.rLock)
} else {
// 等待写锁唤起
m := getg().m
m.schedlink = rw.readers
rw.readers.set(m)
unlock(&rw.rLock)
notesleep(&m.park)
noteclear(&m.park)
}
})
}
}
RWMutex 的 Unlock() 分析
func (rw *rwmutex) Unlock() {
// 将原来被写锁置为负数的 readerCount 重新恢复回来.
r := int32(atomic.Xadd(&rw.readerCount, rwmutexMaxReaders))
if r >= rwmutexMaxReaders {
throw("unlock of unlocked rwmutex")
}
// 唤起之前等待的读锁.
lock(&rw.rLock)
for rw.readers.ptr() != nil {
reader := rw.readers.ptr()
rw.readers = reader.schedlink
reader.schedlink.set(nil)
notewakeup(&reader.park)
r -= 1
}
// 如果 r > 0, 说明读锁队列里有的 goroutine 已不在队列里了,这部分需跳过
rw.readerPass += uint32(r)
unlock(&rw.rLock)
// 解除写锁
unlock(&rw.wLock)
}
RWMutex 的 RUnlock() 分析
func (rw *rwmutex) RUnlock() {
// 如果释放后,readerCount < 0,表示当前写锁正在占有
if r := int32(atomic.Xadd(&rw.readerCount, -1)); r < 0 {
if r+1 == 0 || r+1 == -rwmutexMaxReaders {
throw("runlock of unlocked rwmutex")
}
// readerWait == 0,表示前面的读锁都释放完了,
// 需要唤起写锁
if atomic.Xadd(&rw.readerWait, -1) == 0 {
// The last reader unblocks the writer.
lock(&rw.rLock)
w := rw.writer.ptr()
if w != nil {
notewakeup(&w.park)
}
unlock(&rw.rLock)
}
}
releasem(getg().m)
}
总结
RWMutex 通过 readerCount 的正负来判断当前是处于读锁占有还是写锁占有。
在处于写锁占有状态后,会将此时的 readerCount 赋值给 readerWait,表示要等前面 readerWait 个读锁释放完才算完整的占有写锁,才能进行后面的独占操作。
读锁释放的时候, 会对 readerWait 对应减一,直到为 0 值,就可以唤起写锁了。
并且在写锁占有后,即使有新的读操作加进来, 也不会影响到 readerWait 值了,只会影响总的读锁数目:readerCount。
感兴趣的朋友可以搜一搜公众号「 阅新技术 」,关注更多的推送文章。
可以的话,就顺便点个赞、留个言、分享下,感谢各位支持!
阅新技术,阅读更多的新知识。
边栏推荐
猜你喜欢

Idea view View the class file idea Class folder

《墨者学院——SQL手工注入漏洞测试(MySQL数据库)》

2021-05-22

Introduction to helm basics helm introduction and installation

30. 串联所有单词的子串
Analysis and solution of connection failure caused by MySQL using replicationconnection

巴比特 | 元宇宙每日必读:Meta、微软等科技巨头成立元宇宙标准论坛组织,华为、阿里加入,英伟达高管称欢迎来自加密世界的参与者...

mysql 系列:总体架构概述

【opencv450】椒盐噪声demo

基因检测,如何帮助患者对抗疾病?
随机推荐
AXI_Round_Robin_Arbiter 设计 - AW、W通道部分
Volatile~ variables are not visible under multithreading
Effect evaluation of regression model under credit product quota pricing scenario
微信小程序引导用户添加小程序动画页
MySQL advanced statement I
巴比特 | 元宇宙每日必读:Meta、微软等科技巨头成立元宇宙标准论坛组织,华为、阿里加入,英伟达高管称欢迎来自加密世界的参与者...
The new version of Alibaba Seata finally solves the idempotence, suspension and empty rollback problems of the TCC mode
JS create an array (literal)
2021-04-15
Force deduction solution summary 513- find the value of the lower left corner of the tree
Use of pyqt5 tool box
MySQL 创建和管理表
Unshift() and shift() of JS
RF analyzer demo setup
变压器只能转换交流电,那直流电怎么转换呢?
聚合生态,使能安全运营,华为云安全云脑智护业务安全
LEGO announces price increase, speculators are more excited
K8s-- deploy stand-alone MySQL and persist it
General sequence representation learning in kdd'22 "Ali" recommendation system
mysql 系列:总体架构概述