Go 并发编程实战课
晁岳攀(鸟窝)
前微博技术专家,知名微服务框架 rpcx 作者
25635 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 22 讲
Go 并发编程实战课
15
15
1.0x
00:00/00:00
登录|注册

03|Mutex:4种易错场景大盘点

思考题
总结
流行的Go开发项目踩坑记
Mutex的易错场景
Mutex易错场景知识关系脑图

该思维导图由 AI 生成,仅供参考

你好,我是鸟窝。
上一讲,我带你一起领略了 Mutex 的架构演进之美,现在我们已经清楚 Mutex 的实现细节了。当前 Mutex 的实现貌似非常复杂,其实主要还是针对饥饿模式和公平性问题,做了一些额外处理。但是,我们在第一讲中已经体验过了,Mutex 使用起来还是非常简单的,毕竟,它只有 Lock 和 Unlock 两个方法,使用起来还能复杂到哪里去?
正常使用 Mutex 时,确实是这样的,很简单,基本不会有什么错误,即使出现错误,也是在一些复杂的场景中,比如跨函数调用 Mutex 或者是在重构或者修补 Bug 时误操作。但是,我们使用 Mutex 时,确实会出现一些 Bug,比如说忘记释放锁、重入锁、复制已使用了的 Mutex 等情况。那在这一讲中,我们就一起来看看使用 Mutex 常犯的几个错误,做到“Bug 提前知,后面早防范”。

常见的 4 种错误场景

我总结了一下,使用 Mutex 常见的错误场景有 4 类,分别是 Lock/Unlock 不是成对出现、Copy 已使用的 Mutex、重入和死锁。下面我们一一来看。

Lock/Unlock 不是成对出现

Lock/Unlock 没有成对出现,就意味着会出现死锁的情况,或者是因为 Unlock 一个未加锁的 Mutex 而导致 panic。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

文章总结了Mutex的常见误用场景,包括Lock/Unlock不成对出现、复制已使用的Mutex、重入和死锁。其中,Lock/Unlock不成对出现可能导致死锁或panic;复制已使用的Mutex会导致程序死锁,但可以通过vet工具及时发现并修复;重入和死锁则是常见的并发编程问题。文章还介绍了如何使用vet工具来检查Mutex复制使用问题,以及分析器静态分析实现的原理。此外,还介绍了两种实现可重入锁的方案,以解决Mutex不支持重入的问题。通过对流行的Go开发项目中Mutex相关的Bug进行分析,读者可以了解到即使是经验丰富的开发人员也可能犯错,以及如何避免这些常见的错误,提高并发编程的质量和效率。文章还提到了一些知名的开源项目如Docker、Kubernetes、gRPC和etcd中出现的Mutex相关的问题,以及对应的解决办法。总的来说,本文通过深入分析Mutex的易错场景和实际项目中的Bug,为读者提供了宝贵的经验和教训,帮助他们更好地理解并发编程中的常见问题,以及如何避免和解决这些问题。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Go 并发编程实战课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(38)

  • 最新
  • 精选
  • Remember九离
    第三课代码整理:https://github.com/wuqinqiang/Go_Concurrency/tree/main/class_3

    作者回复: 赞。其他读者可以关注这位朋友的整理

    2020-10-18
    3
    18
  • David
    个人理解:我觉得go里面的可重入锁,有点鸡肋,这也是go官方没有实现的原因吧。第一,如果我加了互斥锁,说明这临界区的资源都是某个groutine独享,那何必要在临界区里面再去请求锁呢,不是多此一举吗,第二,就拿递归来说,我们完全可以把加在递归函数里面的锁,提取到调用递归之前,这样就可以避免递归函数加锁的情况。这是我的个人理解。在redis里面有分布式锁,会出现一个持有锁的线程再次加锁的情况,但是呢,和这里的使用情况还是不一样,redis一般加锁,都会加个有效期(担心忘记释放锁,造成死锁),这个有效期时间长度,不能太大于程序执行时间,这样如果锁来不及释放的时候可能会影响性能,所以一般有效期都和程序执行时间差不多。但是有时候,出现执行时间长超过了有效期的时候,需要续期,才有再次请求锁。以上是我个人理解,如果老师看到评论,可以点评一下我的思维是否有问题

    作者回复: 如果都按照你的设计,是没问题的,但是很多情况下业务比较复杂,会出现a,b,c,b,e,f,c,e,g这样的调用关系,如果a和e都使用了锁,就可能出现重入问题。 也许你觉得这样的错误不可能出现,但是看看大项目中,这种问题还真就出现了

    2020-11-18
    3
    3
  • gitxuzan
    有个地方不明白, 为什么源码里面需要用atomic 原子操作和直接赋值有什么区别

    作者回复: 这个可以等atomic那一讲出来再了解

    2020-10-21
    3
    2
  • 校歌
    Tidb在用mutex的时候特意改成了defer 这种方式,https://github.com/pingcap/tidb/pull/19072, 不过找了个比较老的issue,https://github.com/pingcap/tidb/pull/5171 ,lock和unlock还是没有统一用defer的方式,这个以后可能成为隐患吧。

    作者回复: 👍🏻

    2021-01-29
    1
  • David
    您好老师,在设计可重入锁的时候,在lock方法中, // 延用mutex的加锁机制 m.Mutex.Lock() atomic.StoreInt64(&m.owner, gid) 这个地方有必要 使用atomic吗? 其次,如果有必要,为什么 m.recursion = 1 不用了呢 我个人认为,在锁里面,好像是没必要使用的吧

    作者回复: 第11行未加锁

    2021-01-04
    4
    1
  • Fan
    看了前三节,这门课写的太棒了。继续打卡。

    作者回复: 加油!!!

    2020-12-23
    2
    1
  • 菠萝吹雪—Code
    打卡 作业: https://github.com/pingcap/tidb/issues/27725

    作者回复: 👍🏻

    2022-08-13归属地:北京
  • niceshot
    func (m *TokenRecursiveMutex) Lock(token int64) { if atomic.LoadInt64(&m.token)==token{ m.recursion++ return } m.Mutex.Lock() atomic.StoreInt64(&m.token,token) m.recursion = 1 } 这里如果调用者提供前后两次两个不同的token Mutex.Lock()不就调用两次了吗

    作者回复: 第二次不就阻塞在这里了么?

    2020-10-30
    2
  • 橙子888
    更新地好快,上一讲的源码还没消化完,新的一讲又出了……

    作者回复: 又看到你打卡了

    2020-10-16
  • Junes
    分享一个我觉得很有项目借鉴意义的PR吧: https://github.com/pingcap/tidb/pull/20381/files 这个问题是在当前的函数中Lock,然后在调用的函数中Unlock。这种方式会导致,如果运行子函数时panic了,而外部又有recover机制不希望程序崩溃,就触发不到Unlock,引起死锁。 PR中加了个recover处理,并且判断recover有error才Unlock,这是一种处理方法。 理想的设计,是将子函数的Unlock挪到与Lock平级的代码,或者不进行recover处理,Let it panic后修复问题。但大型项目项目经常会因为逻辑错综复杂或者各种历史原因,不好改动吧,这种处理方式虽然不好看,但能解决问题,有时候也挺无奈的~
    2020-10-16
    64
收起评论
显示
设置
留言
38
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部