type Mutex struct {
state int32
sema uint32
}
-
Mutex.state
表示互斥锁的状态,比如是否被锁定等,它是一个32位的整型变量,内部实现时把该变量分成4份:- Locked
表示该Mutex 是否被锁定,0表示没有锁定,1表示已被锁定。 - Woken
表示是否有协程已被唤醒,0表示没有协程唤醒,1表示已有协程唤醒,正在加锁过程中。 - Starving
表示该Mutex 是否处于饥饿状态,0表示没有接,1表示饥饿状态,说明有协程阻塞了超过1ms。 - Waiter
表示阻塞等待锁的协程个数,协程解锁时根据此值来判断是否需要释放信号量。
协程之间抢锁实际上是争抢给Locked赋值的权利,能给Locked域置1,就说明抢锁成功。抢不到就阻塞等待 信号量,一旦持有锁的协程解锁,那么等待的协程就会依次被唤醒。
image.png
- Locked
-
Mutex.sema
表示信号量,协程阻塞等待该信号量,解锁的协程释放信号量从而唤醒等待信号量的协程
简单加锁

加锁被阻塞

简单解锁

解锁并唤醒协程

自旋
加锁时,如果当前的 Locked 位为1,说明当前该锁由其他协程持有,尝试加锁的协程并不马上转入阻塞,而是会持续地探测 Locked 位是否变为0,这个过程称为自旋。
自旋地时间很短,如果在自旋过程发现锁已经被释放,那么协程可以立即获取锁,此时即便有协程被唤醒,也无法获取锁,只能再次阻塞。自旋的好处是,当加锁失败时不必立即转入阻塞,有一定机会获取到锁,这样可以避免协程的切换。
- 自旋条件
加锁时程序会自动判断是否可以自旋,无限制的自旋将给CPU带来巨大的压力,所以判断是否自旋就很重要了。自旋必须满足下面的所有条件:- 自旋次数必须要足够少,通常为4,即自旋最多4次
- CPU 核数要大于1,否则自旋没有意义,因此此时不可能有其他协程释放锁
- 协程调度机制中的 Process 数量要大于1,比如使用 将处理器设置为1就不能启用自旋
- 协程调度机制中的可运行队列必须为空,否则会延迟协程调度
mutex 模式
- 自旋的问题
如果自旋过程中获得的锁,那么之前被阻塞的协程将无法获得锁,如果加锁的协程特别多,每次都通过自旋获得锁,那么之前被阻塞的协程将很难获得锁,从而进入饥饿状态,为了避免协程长时间无法获取锁,自1.8版本之后增加了模式来解决。
每个Mutex 都有2种模式,称为 Normal 和 Starving
-
Normal
Mutex 默认的模式,在该模式下,协程如果加锁不成功则不会立即转入阻塞排队,而是判断是否满足自旋的条件,如果满足则会启动自旋过程,尝试抢锁。 -
Starving
自旋过程种抢到锁,一定意味着同一时刻同一时刻有协程释放了锁。释放锁时如果发现有阻塞等待的协程,那么还会释放一个信号量来唤醒一个等待的协程,被唤醒的协程得到了CPU后开始运行,此时发现锁已经被抢占了,自己只好再次阻塞,不过阻塞前会判断自上次阻塞到本次阻塞经过了多长时间,如果超过了1ms,则会将Mutex 标记为 Starving 模式,然后再次阻塞。
woken 状态
Woken 状态用于加锁和解锁过程中的通信,同一时刻,两个协程一个在加锁,另一个在解锁,在加锁的协程可能在自旋过程中,此时把woken 标记为1,用于通知解锁协程不必释放信号量。
网友评论