GoLang之Mutex底层系列三(lock的slowpath、unlock的slowpath)

GoLang之Mutex底层系列三(lock的slowpath、unlock的slowpath)

当一个goroutine尝试给mutex加锁时,如果其他goroutine已经加了锁还没有释放,而且当前mutex工作在正常模式下,是不是就要开始自旋了呢?

在这里插入图片描述

不一定,因为如果当前是单核场景,自旋的goroutine在等待持有锁的goroutine释放锁,而持有锁的goroutine在等待自旋的goroutine让出CPU,这种情况下自旋是没有意义的。而且如果GOMAXPROCS=1,或者当前没有其它正在运行的P,也和单核场景类似,同样不需要自旋。

在这里插入图片描述

除此之外,如果当前P的本地runq不为空,相较于自旋而言,切换到本地runq更有效率,所以为保障吞吐量也不会自旋。

在这里插入图片描述

最终,只有在多核场景下,且GOMAXPROCS>1,至少有一个其他的P处于running,当前P的本地runq为空的情况下,才可以自旋。

在这里插入图片描述

进入自旋的goroutine会先去争抢mutex的唤醒标识位,设置mutexWoken标识位的目的是:在正常模式下,告知持有锁的goroutine,在unlock的时候不用再唤醒其他goroutine了,已经有goroutine在这里等待,以免唤醒太多的等待协程。mutex中的自旋,底层是通过procyield循环执行30次PAUSE,自旋次数上限为4,而且每自旋一次都要重新判断是否可以继续自旋。

在这里插入图片描述

如果锁被释放了,或者锁进入了饥饿模式,亦或者已经自旋了4次,都会结束自旋。

在这里插入图片描述

结束自旋或者根本不用自旋的goroutine,就该尝试原子操作修改mutex的状态了。把此时mutex.state保存了old中,把要修改为的新state记为new。
如果old处于饥饿模式或者加锁状态,goroutine就得去排队,所以在这些情况下排队规模要加1.

在这里插入图片描述

如果是正常模式,就要尝试设置lock位,所以最后一位要置为1

在这里插入图片描述

如果当前goroutine等待的时间已经超过1ms,而且锁还没被释放,就要将mutex的状态切换为饥饿模式,注意,这里之所有还要求锁没被释放,是因为如果锁已经释放了,那怎么都得去抢一次啊,要是直接进入饥饿模式就只能去排队了。

在这里插入图片描述
在这里插入图片描述

把排队规模和几个标志位都设置好以后,在执行原子操作修改state之前,若是当前goroutine持有唤醒标识的话,还要将唤醒标识位重置。因为接下来无论是要去抢锁,还是单纯的去排队,如果原子操作成功了,那么要么是成功抢到了锁,要么是成功进到了等待队列里。当前goroutine都不再是被唤醒的goroutine了,所以要释放唤醒标识。

在这里插入图片描述

而如果原子操作不成功,也就意味着其他goroutine在我们保存mutex.state到old以后,又修改了state的值,当前goroutine就要回过头去,继续从自旋检测这里开始再次尝试。所以也需要释放自己之前抢到的唤醒标识位,从头再来。

在这里插入图片描述

继续展开这个原子操作成功的分支,如果是抢锁操作成功了,那么加锁的slow path就可以宣告结束了。

在这里插入图片描述

如果是排队的规模设置成功了,还要决定是排在等待队列头部还是尾部,如果当前goroutine已经排过队,是在unlock中唤醒的,那就排在等待队列头部。

在这里插入图片描述

如果是第一次排队,就得排到等待队列尾部,并且从第一次排队开始记录当前goroutine的等待时间。

在这里插入图片描述

接下来就会让出,进到等待队列里面了(runtime_SemacquireMutex(&m.sema, queueLifo, 1)),队列里的goroutine被唤醒时(unlock),要从上次让出的地方开始继续执行,

在这里插入图片描述

接下来会判断,如果mutex处在正常模式,那就接着从自旋开始抢锁,如果唤醒后的mutex处在饥饿模式,那就没有其他goroutine会和自己抢了,锁已经轮到自己这里,只需要把mutex.state中lock标识位设置为加锁,把等待队列规模减去1,再看看是不是要切换到正常模式,也就是自己的等待时间是不是小于1ms,或者等待队列已经空了,最后设置好mutex.state就一切ok了,这就是加锁操作的slow path。

在这里插入图片描述

接下来看unlock的slow path,进到unlock的slow path,说明除去lock标识为以外,剩下的位不全为0,如果处在正常模式,若等待队列为空,或已经有goroutine被唤醒或者获得了锁,或者锁进入了饥饿模式,那就不需要唤醒某个goroutine了,直接返回即可。否则就要尝试抢占mutexWoken标识位,获取唤醒一个goroutine的权利,抢占成功后,就会通过runtime_Semrelease函数唤醒一个goroutine,如果抢占不成功就进行循环尝试,直到等待队列为空,或已经有goroutine被唤醒或者获得了锁,或者锁进入了饥饿模式,则退出循环,而在饥饿模式下,后来的goroutine不会争抢锁,而是直接排队,锁的所有权是直接从执行unlock的goroutine,传递给等待队列中首个等待者的,所以不用抢占mutexWoken标识位。

在这里插入图片描述

第一个等待者唤醒后,会继承当前goroutine的时间片立刻开始运行,也就是继续lockSlow中这里,goroutine被唤醒以后的逻辑,这就是unlock的slow path。

在这里插入图片描述
在这里插入图片描述

接下来我们再看看关于信号量的相关操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GoGo在努力

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值