深入理解Go中的Mutex机制

愉快的生活从一篇文章开始,虽然Go的主题月已经结束了,我也如愿以偿拿到了我的瓷缸杯,但是,我还是要继续输出文章,可能这就是❤️ 吧

前言

今天的话,我想聊一下Go中对于Mutex的一个概念,从字面上我们可以看出,它就是一个? 的概念,而在Go中主要是goroutine并发的操作同一个资源单位的时候,使用Mutex,而今天我们也从Mutex的结构开始,一步步来了解Go中的Mutex。

正文

我们知道Mutex在Go中是被放在sync包里面的,其中它的结构也非常的简单,大概长这样

type Mutex struct {
	state int32
	sema  uint32
}
复制代码

其中state置为0的时候为未加锁的状态,而为1的时候,则是加了锁的状态。

另一个参数则是信号量,它的作用主要是用来在争抢锁的失败之后排队用的(后面细说)。

抢锁的模式

在Go一共可以分为两种抢锁的模式,一种是正常模式,另外一种是饥饿模式

正常模式

在刚开始的时候,是处于正常模式,也就是,当一个G1持有着一个锁的时候,G2会自旋的去尝试获取这个锁

微信截图_20210630234457.png

自旋超过4次还没有能获取到锁的时候,这个G2就会被加入到获取锁的等待队列里面,而这个G在队列中的顺序就是根据信号量来决定的。

微信截图_20210630235219.png

这时候,如果G1释放了这个锁,这时候,G2就会被从队列中被唤醒,然后去竞争这个锁,这时候,不只是有一个G2在对锁进行争抢,还会有其他正在自旋中的G也在争抢这把锁。

微信截图_20210630235757.png

这时候,因为G2是被唤醒来去抢锁的,相对于在CPU中本身已经的自旋状态的G4和G5,它更不容易获得这个锁,那么这时候,如果它获取锁失败的时候,G2就会被重新加入到等待队列的头部,继续下一轮的等待。

但是很明显,这样是不行的,这样会造成队列中的G长时间得不到执行,所以这时候就有了饥饿模式

饥饿模式

当处于等待队列中的Goroutine超过1ms没有获取到锁的时候,它的状态就会变成饥饿模式,这个时候,如果G1把锁给释放出来的时候,G2是可以直接获取到这个锁的,而不用通过去争抢的方式,同时,后续的G4和G5也不会进入自旋的状态,而是直接的就进入到了等待队列后面去了

微信截图_20210701002057.png

那么也不可能说永远的保持一个饥饿的状态,总归会有吃饱的时候,也就是总有那么一刻Mutex会回归到正常模式,那么回归正常模式必须具备的条件有以下几种

1.G2的执行时间小于1ms

2.处于等待队列已经全部清空了

当满足上述两个条件的任意一个的时候,Mutex会切换回正常模式,而Go的抢锁的过程,就是在这个正常模式和饥饿模式中来回切换进行的。

在正常模式下,程序会让Goroutine有自旋的这一个状态,是因为为了避免频繁的唤起和睡眠Goroutine的过程中带来的大的开销,通过自旋+队列排队的方式,来增加更大的一个吞吐量,但是这种方式,很容易会出现一个尾端延迟执行的情况。

而在饥饿模式下,则会把Goroutine加入到队列中去,严格的按照顺序执行,这样就不会带来尾端延迟的问题,但相对应的,程序的吞吐量也就没有正常模式下来的高了。

Goroutine的自旋

前面我们提到了,在正常模式下,新的Goroutine是会进入一个自旋的状态去抢锁的,那么是不是只要是新的Goroutine都会进入一个自旋的状态呢,其实也不是,想要进入自旋的状态必须满足下面的几个条件。

  1. 必须是一个多核的机子

  2. gomaxproces必须大于1

  3. 除了正在运行的P以外,还必须要有其他的P也在处于running的状态

  4. 当前run P的本地队列为空

只有同时满足了这四个条件,这个Goroutine才会进入一个自旋的状态。

最后

最近在看Go中的关于锁的一些源码,还有一些小视频,在这里做一些总结,如果你觉得这篇文章对你有所帮助,点个赞再走呀!!!
下载.jpg

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享