当前位置:网站首页>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查看.class文件 idea查看.class文件夹
- 2021-05-08
- 物流贸易相关
- Force deduction solution summary 513- find the value of the lower left corner of the tree
- Millions of bonuses are waiting for you to get. The first China Yuan universe innovation and application competition is in hot Recruitment!
- RF analyzer demo setup
- 这五年的6个编程感悟!
- 建議自查!MySQL驅動Bug引發的事務不回滾問題,也許你正面臨該風險!
- WebService interface publishing and calling
猜你喜欢

小米为何深陷芯片泥潭?

KDD'22「阿里」推荐系统中的通用序列表征学习

The new version of Alibaba Seata finally solves the idempotence, suspension and empty rollback problems of the TCC mode

The well-known face search engine provokes public anger: just one photo will strip you of your pants in a few seconds

2021-05-22

乐高宣布涨价,炒家更嗨皮了

基因检测,如何帮助患者对抗疾病?

Millions of bonuses are waiting for you to get. The first China Yuan universe innovation and application competition is in hot Recruitment!

The work and development steps that must be done in the early stage of the development of the source code of the live broadcasting room

How to solve the problem that iterative semi supervised training is difficult to implement in ASR training? RTC dev Meetup
随机推荐
重卡界销售和服务的“扛把子”,临沂广顺深耕产品全生命周期服务
2021-05-08
When I went to oppo for an interview, I got numb...
《墨者学院——SQL手工注入漏洞测试(MySQL数据库)》
进销存软件排行榜前十名!
golang--文件的多个处理场景
Auto - vérification recommandée! Les bogues MySQL ne font pas reculer les transactions, peut - être êtes - vous à risque!
Starting from 3, add paging function in the business system
Unshift() and shift() of JS
2021-04-15
Une compréhension simple du tri rapide
知名人脸搜索引擎惹众怒:仅需一张照片,几秒钟把你扒得底裤不剩
一款自动生成单元测试的 IDEA 插件
2021-04-15
SQL injection vulnerability (principle)
[opencv450] salt and pepper noise demo
微信小程序引导用户添加小程序动画页
Pop() element in JS
详解Redis分布式锁的原理与实现
golang 重要知识:context 详解