03|Mutex:4种易错场景大盘点
晁岳攀/鸟窝
该思维导图由 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
《Go 并发编程实战课》,新⼈⾸单¥59
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
全部留言(38)
- 最新
- 精选
- Remember九离第三课代码整理:https://github.com/wuqinqiang/Go_Concurrency/tree/main/class_3
作者回复: 赞。其他读者可以关注这位朋友的整理
2020-10-18318 - David个人理解:我觉得go里面的可重入锁,有点鸡肋,这也是go官方没有实现的原因吧。第一,如果我加了互斥锁,说明这临界区的资源都是某个groutine独享,那何必要在临界区里面再去请求锁呢,不是多此一举吗,第二,就拿递归来说,我们完全可以把加在递归函数里面的锁,提取到调用递归之前,这样就可以避免递归函数加锁的情况。这是我的个人理解。在redis里面有分布式锁,会出现一个持有锁的线程再次加锁的情况,但是呢,和这里的使用情况还是不一样,redis一般加锁,都会加个有效期(担心忘记释放锁,造成死锁),这个有效期时间长度,不能太大于程序执行时间,这样如果锁来不及释放的时候可能会影响性能,所以一般有效期都和程序执行时间差不多。但是有时候,出现执行时间长超过了有效期的时候,需要续期,才有再次请求锁。以上是我个人理解,如果老师看到评论,可以点评一下我的思维是否有问题
作者回复: 如果都按照你的设计,是没问题的,但是很多情况下业务比较复杂,会出现a,b,c,b,e,f,c,e,g这样的调用关系,如果a和e都使用了锁,就可能出现重入问题。 也许你觉得这样的错误不可能出现,但是看看大项目中,这种问题还真就出现了
2020-11-1833 - gitxuzan有个地方不明白, 为什么源码里面需要用atomic 原子操作和直接赋值有什么区别
作者回复: 这个可以等atomic那一讲出来再了解
2020-10-2132 - 校歌Tidb在用mutex的时候特意改成了defer 这种方式,https://github.com/pingcap/tidb/pull/19072, 不过找了个比较老的issue,https://github.com/pingcap/tidb/pull/5171 ,lock和unlock还是没有统一用defer的方式,这个以后可能成为隐患吧。
作者回复: 👍🏻
2021-01-291 - David您好老师,在设计可重入锁的时候,在lock方法中, // 延用mutex的加锁机制 m.Mutex.Lock() atomic.StoreInt64(&m.owner, gid) 这个地方有必要 使用atomic吗? 其次,如果有必要,为什么 m.recursion = 1 不用了呢 我个人认为,在锁里面,好像是没必要使用的吧
作者回复: 第11行未加锁
2021-01-0441 - Fan看了前三节,这门课写的太棒了。继续打卡。
作者回复: 加油!!!
2020-12-2321 - 菠萝吹雪—Code打卡 作业: https://github.com/pingcap/tidb/issues/27725
作者回复: 👍🏻
2022-08-13归属地:北京 - niceshotfunc (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-302 - 橙子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-1664
收起评论