Go 并发编程实战课
晁岳攀(鸟窝)
前微博技术专家,知名微服务框架 rpcx 作者
新⼈⾸单¥9.9
1839 人已学习
课程目录
已更新 3 讲 / 共 22 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 想吃透Go并发编程,你得这样学!
免费
基本并发原语 (2讲)
01 | Mutex:如何解决资源并发访问问题?
02 | Mutex:庖丁解牛看实现
Go 并发编程实战课
15
15
1.0x
00:00/00:00
登录|注册

02 | Mutex:庖丁解牛看实现

晁岳攀 2020-10-14
你好,我是鸟窝。
上一讲我们一起体验了 Mutex 的使用,竟是那么简单,只有简简单单两个方法,Lock 和 Unlock,进入临界区之前调用 Lock 方法,退出临界区的时候调用 Unlock 方法。这个时候,你一定会有一丝好奇:“它的实现是不是也很简单呢?”
其实不是的。如果你阅读 Go 标准库里 Mutex 的源代码,并且追溯 Mutex 的演进历史,你会发现,从一个简单易于理解的互斥锁的实现,到一个非常复杂的数据结构,这是一个逐步完善的过程。Go 开发者们做了种种努力,精心设计。我自己每次看,都会被这种匠心和精益求精的精神打动。
所以,今天我就想带着你一起去探索 Mutex 的实现及演进之路,希望你能和我一样体验到这种技术追求的美妙。我们从 Mutex 的一个简单实现开始,看看它是怎样逐步提升性能和公平性的。在这个过程中,我们可以学习如何逐步设计一个完善的同步原语,并能对复杂度、性能、结构设计的权衡考量有新的认识。经过这样一个学习,我们不仅能通透掌握 Mutex,更好地使用这个工具,同时,对我们自己设计并发数据接口也非常有帮助。
那具体怎么来讲呢?我把 Mutex 的架构演进分成了四个阶段,下面给你画了一张图来说明。
初版”的 Mutex 使用一个 flag 来表示锁是否被持有,实现比较简单;后来照顾到新来的 goroutine,所以会让新的 goroutine 也尽可能地先获取到锁,这是第二个阶段,我把它叫作“给新人机会”;那么,接下来就是第三阶段“多给些机会”,照顾新来的和被唤醒的 goroutine;但是这样会带来饥饿问题,所以目前又加入了饥饿的解决方案,也就是第四阶段“解决饥饿”。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Go 并发编程实战课》,如需阅读全部文章,
请订阅文章所属专栏新⼈⾸单¥9.9
立即订阅
登录 后留言

精选留言(13)

  • 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,单从内存空间来说已经要求极高了,当前的设计肯定可以满足了。

    作者回复: 赞学习态度,赞作业

    2020-10-14
    4
  • 小龙虾
    打卡
    2020-10-14
    1
  • 大力水手
    打卡
    2020-10-15
  • NevS
    老师,我有一个疑问。在第二版的mutex的Unlock()方法里面:
    1、第3行【new := atomic.AddInt32(&m.state, -mutexLocked) 】,此时的new已经减去了mutexLocked,去掉了锁被持有的标志位。
    2、然后在第8行,将new赋值给了old。
    3、最后在第10行中有【old&(mutexLocked|mutexWoken) != 0】的判断,但是old已经在第3行里面减去了mutexLocked,为什么这里还需要判断mutexLocked,用【old&(mutexWoken) != 0】应该就可以了吧?因为按照我的理解old的第0位bit应该是0,不可能是1了吧。

    作者回复: 因为它在for循环中,old的值会改变,第18行 。其它goroutine可能又获取了锁 。

    2020-10-14
  • 老师您好,有个疑问。
    runtime_Semrelease 信号唤起的总队queue中的第一个吗?
    争抢锁在非饥饿模式下,是不是只有队首的waiter和新的goroutine之间发生?
    谢谢

    作者回复: 是的。你可以看runtime中的实现https://github.com/golang/go/blob/0a820007e70fdd038950f28254c6269cd9588c02/src/runtime/sema.go#L321

    2020-10-14
  • 那时刻
    老师对于mutex讲的很仔细,之前看过mutex源码,有些地方看的不是明白,看了老师的讲解后,清晰了很多。

    请问老师一个问题,从 1.14 版本起,Go 对 defer 做了优化,采用更有效的内联方式,这种内联方式和lock里slowlock内联方式一样么?

    另外,go里的内联和C++内联有什么区别吗?

    作者回复: 应该说1.14中才采用了内敛的方式,官方专门有文档介绍这个,国内也有一些介绍,比如https://pengrl.com/p/20023/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

    2020-10-14
  • Geek_7ef408
    底层实现的代码和业务代码果然有很大不同,理解起来也更加困难,光是上面那一堆位运算就已经够让人头疼的了,全程懵逼的看完了这一篇,还需要好好消化一下

    作者回复: 没错,多来几遍,每次都有收获

    2020-10-14
  • buckwheat
    这源码看的感觉好难啊,尤其是这种并发的源码,老师有什么好的建议吗?

    作者回复: Mutex和channel实现代码实现非常复杂,第一遍看不懂没关系,第二遍看不懂也没关系......,永远看不懂也不影响你使用它。你可以每次只尝试理解一个知识点。我的经验是多看几遍,每一个if分支都理解它的意思,在纸上画一画state的值的变化

    2020-10-14
  • 光明
    Mutex 的 state 是 int32。

    第1位 mutexLocked 表示是否为锁定状态
    第2位 mutexWoken 表示锁是否处于唤醒
    第3位 mutexStarving 表示是否饥饿

    因此,32 - 3 = 29 位,也就是 2 ^ 29 个 goroutine,再加上内存限制考虑,感觉是够用的。
    2020-10-14
  • Alexdown
    1. 现在一个 state 的字段被划分成了阻塞等待的 waiter 数量(右移mutexWaiterShift位得到)、饥饿标记(mutexStarving)、唤醒标记(mutexWoken)和持有锁的标记(mutexLocked)四个部分
    2. 最大2^29-1个goroutine等待mutex,约为5.37*10^8个,够用了。
    2020-10-14
  • 星亦辰
    这一讲有点儿烧脑了 , 需要仔细看看

    作者回复: 是的,这一讲需要慢慢品,以后还可以翻过来回味一下

    2020-10-14
  • little_code
    打卡

    作者回复: 继续,加油

    2020-10-14
  • 橙子888
    熬夜学习01课时后发现02课时也出来了,先来抢个沙发。

    作者回复: 你第三讲打卡了,加油

    2020-10-14
收起评论
13
返回
顶部