为了账号安全,请及时绑定邮箱和手机立即绑定

【九月打卡】第16天 go的锁--锁饥饿、读写锁

标签:
Go

课程名称:深入Go底层原理,重写Redis中间件实战


课程章节:6-5、6-6、6-7、6-8


课程讲师:Moody


课程内容:

 ※ 锁饥饿

sync.Mutex 锁,当协程获取锁一直获取不到,超过1ms,就会切换到饥饿模式。

锁饥饿模式:

  • 新来的协程如果没有得到锁,不再自旋等待,而是直接进入休眠队列

  • 休眠队列里面被唤醒的协程,不需要继续竞争锁,而是直接得到锁

  • 当sema的休眠队列已经清空,那么锁将从饥饿模式中退出,恢复到正常模式

※ 使用锁要注意

  1. 尽量减少锁的使用时间,只锁关键点

  2. 善用defer来释放锁,防止业务panic导致的锁无法释放问题

---------------

※ 读写锁

  • 读锁是共享锁,写锁是独享锁

  • 当写锁没有上的时候,多个协程可以同时持有一把读锁。

  • 读锁没有全部释放,写锁不能添加,只能进入等待队列

  • 写锁同时只能有一个协程持有,后面希望持有该写锁的协程都会进入等待队列

  • 写锁被锁定的时候,读锁不能上,防止出现脏读

https://img1.sycdn.imooc.com//63281af60001a1ad11240699.jpg

  1. w  互斥锁为写锁

  2. writerSem 作为写协程队列

  3. readerSem 作为读协程队列

  4. readerCount  正值:持有读锁的读协程的个数   负值:加了写锁

  5. readerWait  当写操作被阻塞时等待的读协程的个数

※ 写锁

加写锁:有读锁的情况

sync.RWMutex.Lock方法

func (rw *RWMutex) Lock() {

rw.w.Lock()

r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders

if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {

runtime_SemacquireMutex(&rw.writerSem, false, 0)

}

}

  1. 调用Lock方法会阻塞后续的写操作,因为互斥锁已经被获取,所以其他协程获取写锁的时候会进入自旋或者进入休眠队列

  2. 调用atomic.add减去一个rwmutexMaxReaders,这个rwmutexMaxReaders的值是1<<30,数字非常大。再加上rwmutexMaxReaders,得到原值r。这一步的操作主要是先加,加成功后才有后续操作。这一步主要是判断 readerCount是否为0,如果不为0,则说明仍然有读协程持有读锁。

  3. 如仍有其他的协程持有读锁,那么写协程就进入休眠等待,执行runtime_SemacquireMutex方法。

  4. 所有的读锁释放之后,才会放出writerSem信号量唤醒写的休眠队列。

    https://img1.sycdn.imooc.com//632848670001cdbd18421406.jpg

解写锁:

sync.RWMutex.Unlock方法


func (rw *RWMutex) Unlock() {

r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)

if r >= rwmutexMaxReaders {

race.Enable()

throw("sync: Unlock of unlocked RWMutex")

}

for i := 0; i < int(r); i++ {

runtime_Semrelease(&rw.readerSem, false, 0)

}

rw.w.Unlock()


}

  1. 调用atomic.AddInt32方法把readerCount还原为正数,如果大于rwmutexMaxReaders,说明根本就是在读锁的状态,完全不需要释放写锁,弹出去。注意throw是底层runtime包才有的报错,和panic不一样,他会直接down掉整个进程,属于无法通过 defer 和 recover 捕获的崩溃,需要从系统的角度重新把进程拉起来。

  2. 通过for循环,释放掉所有因写锁导致进入等待队列的读协程,读锁是共享锁,所以可以一次释放掉所有的读锁。readerWait还原为0

  3. rw.w.Unlock私房掉w的锁。

    https://img1.sycdn.imooc.com//63284b56000187ff17521414.jpg

※ 读锁

加读锁:readerCount > 0 :这种情况非常简单,如果readerCount大于0,说明已经有其他协程获取了读锁,写锁肯定因为互斥进入了休眠队列,那么直接readerCount+1,读锁加上就行了。

如果readerCount < 0 :则说明当前已经加了写锁,就会进入到休眠队列,并给readerWait+1

解读锁:readerCount >= 0 : 说明当前还有其他协程拿了读锁,所以可以直接解除readerWait,readerCount都减去1,只有最后一个读协程解锁后,判断readerCount-1=0,那么就去看写队列有没有协程等待拿写锁,唤醒,拿写锁。

如果readerCount < 0:

有一个写锁正在执行,这时候会调用sync.RWMutex.rUnlockSlow方法

func (rw *RWMutex) rUnlockSlow(r int32) {

if r+1 == 0 || r+1 == -rwmutexMaxReaders {

throw("sync: RUnlock of unlocked RWMutex")

}

if atomic.AddInt32(&rw.readerWait, -1) == 0 {

runtime_Semrelease(&rw.writerSem, false, 1)

}

}


此方法会减少获取锁的写操作等待的读操作数readerWait-1,意思是,我不去竞争这个锁了,我不排队了。



点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
PHP开发工程师
手记
粉丝
2
获赞与收藏
4

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消