02 | Mutex:庖丁解牛看实现
晁岳攀

你好,我是鸟窝。
上一讲我们一起体验了 Mutex 的使用,竟是那么简单,只有简简单单两个方法,Lock 和 Unlock,进入临界区之前调用 Lock 方法,退出临界区的时候调用 Unlock 方法。这个时候,你一定会有一丝好奇:“它的实现是不是也很简单呢?”
其实不是的。如果你阅读 Go 标准库里 Mutex 的源代码,并且追溯 Mutex 的演进历史,你会发现,从一个简单易于理解的互斥锁的实现,到一个非常复杂的数据结构,这是一个逐步完善的过程。Go 开发者们做了种种努力,精心设计。我自己每次看,都会被这种匠心和精益求精的精神打动。
所以,今天我就想带着你一起去探索 Mutex 的实现及演进之路,希望你能和我一样体验到这种技术追求的美妙。我们从 Mutex 的一个简单实现开始,看看它是怎样逐步提升性能和公平性的。在这个过程中,我们可以学习如何逐步设计一个完善的同步原语,并能对复杂度、性能、结构设计的权衡考量有新的认识。经过这样一个学习,我们不仅能通透掌握 Mutex,更好地使用这个工具,同时,对我们自己设计并发数据接口也非常有帮助。
那具体怎么来讲呢?我把 Mutex 的架构演进分成了四个阶段,下面给你画了一张图来说明。
“初版”的 Mutex 使用一个 flag 来表示锁是否被持有,实现比较简单;后来照顾到新来的 goroutine,所以会让新的 goroutine 也尽可能地先获取到锁,这是第二个阶段,我把它叫作“给新人机会”;那么,接下来就是第三阶段“多给些机会”,照顾新来的和被唤醒的 goroutine;但是这样会带来饥饿问题,所以目前又加入了饥饿的解决方案,也就是第四阶段“解决饥饿”。
公开
同步至部落
取消
完成
0/2000
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Go 并发编程实战课》,新⼈⾸单¥59
《Go 并发编程实战课》,新⼈⾸单¥59
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
全部留言(105)
- 最新
- 精选
- Junes老师讲得太棒了,我自己看Mutex源码时,没有前因后果,知识不成体系。交个作业: 1. 目前 Mutex 的 state 字段有几个意义,这几个意义分别是由哪些字段表示的? 和第四个阶段的讲解基本一致:前三个bit分别为mutexLocked、mutexWoken、mutexStarving,剩余bit表示mutexWaiter 2. 等待一个 Mutex 的 goroutine 数最大是多少?是否能满足现实的需求? 单从程序来看,可以支持 1<<(32-3) -1 ,约 0.5 Billion个 其中32为state的类型int32,3位waiter字段的shift 考虑到实际goroutine初始化的空间为2K,0.5Billin*2K达到了1TB,单从内存空间来说已经要求极高了,当前的设计肯定可以满足了。
作者回复: 赞学习态度,赞作业
7102 - buckwheat这源码看的感觉好难啊,尤其是这种并发的源码,老师有什么好的建议吗?
作者回复: Mutex和channel实现代码实现非常复杂,第一遍看不懂没关系,第二遍看不懂也没关系......,永远看不懂也不影响你使用它。你可以每次只尝试理解一个知识点。我的经验是多看几遍,每一个if分支都理解它的意思,在纸上画一画state的值的变化
37 - Dragon Frog老师,你好我有个疑问。在第二版中的 mutexWoken 这个含义到底该怎么理解。没办法很好的理解这个字段的作用
作者回复: 标记是否有“通过unlock唤醒”的waiter在竞争锁
12 - 锋老师您好,有个疑问。 runtime_Semrelease 信号唤起的总队queue中的第一个吗? 争抢锁在非饥饿模式下,是不是只有队首的waiter和新的goroutine之间发生? 谢谢
作者回复: 是的。你可以看runtime中的实现https://github.com/golang/go/blob/0a820007e70fdd038950f28254c6269cd9588c02/src/runtime/sema.go#L321
28 - 授人以🐟,不如授人以渔老师,有一个关于编程的疑惑,很难解决:我以后可能也会写一些复杂的逻辑,特别是并发的逻辑,但如何用逻辑思维去证明代码是没有问题的?比如第二版本的 Mutex 代码,就写的很好。
作者回复: 一点建议 一个是多学习并发的知识,充分了解并发的复杂性和场景。 二是实现并发原语的时候,要便利各种可能性
4 - Leo看第一遍的时候真的一脸懵,所以想起了老师开篇讲的很重要的一句“先建立体系”,所以我第二遍没有去关注源码,而是去理解老师文档中对锁实现过程的描述找到大体脉络。也就是先理解思路,再去逐个追究源码的细节。所以第三遍就能很顺利理解Mutex第一个版本的实现,第二个版本因为涉及到位运算,所以需要先把go的位运算基础搞清楚再继续分析。虽然还没有完全弄懂整个Mutex的实现,但现在也收获良多,因为从源码以及老师的文档描述中包含了程序的策略,而这些策略也可以用于我们开发中解决其他问题。
作者回复: 👍🏻
3 - 消逝文字底层实现的代码和业务代码果然有很大不同,理解起来也更加困难,光是上面那一堆位运算就已经够让人头疼的了,全程懵逼的看完了这一篇,还需要好好消化一下
作者回复: 没错,多来几遍,每次都有收获
3 - Felix我有一件事想不明白,如果大家用的是同一个s ync.Mutex,那么在每个goroutine中的sema数值不都是一样的,如果其中一个goroutine中的mutexLocked变为1,所有goroutine中mutexLocked不都是变为1吗,我的理解是mutex没有用值传递,应该指向同一块内存区域啊。
作者回复: 对,所有的goroutine看到的都是一样的
2 - 阿牛交作业 1、目前 Mutex 的 state 字段有几个意义,这几个意义分别是由哪些字段表示的? state有四个字段: • mutexLocked 占1bit,持有锁标记 • mutexWoken 占1bit,唤醒标记 • mutexStarving 占1bit,饥饿标记 • mutexWaiters 阻塞等待的waiter数量 2、等待一个 Mutex 的 goroutine 数最大是多少?是否能满足现实的需求? 32-3=29位=2^29=536 870 912
作者回复: 加油
2 - 果酱new = old + 1<<mutexWaiterShift. 1<<mutexWaiterShift这个结果是4 为啥大佬注释说的是加1啊 求解
作者回复: waiter数从第4个bit开始
22
收起评论