Go Mutex 饥饿模式:

相关概念:

原子操作
指那些不能够被打断的操作被称为原子操作,当有一个CPU在访问这块内容地址时,其他CPU就不能访问。

互斥锁
用于保护临界区,确保同一时间只有一个线程访问数据(挂起,通过休眠来使线程阻塞)。

自旋锁
指当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断地判断锁能否够被成功获取,直到拿到锁才会退出循环。获取锁的线程持续活跃,不挂起(不是通过休眠来使进程阻塞),继续占有cpu

饥饿问题
一些悲惨的G长时间获取不到锁,导致业务逻辑不能继续完整执行。而当前正在cpu上运行的goroutine可能会更先获取到锁,比如自旋锁。

描述

公平性

正常模式

所有goroutine按照FIFO的顺序进行锁获取,被唤醒的goroutine和新请求锁的goroutine同时进行锁获取,通常新请求锁的goroutine更容易获取锁(持续占有cpu),被唤醒的goroutine则不容易获取到锁

    否

饥饿模式

所有尝试获取锁的goroutine进行等待排队,新请求锁的goroutine不会进行锁获取(禁用自旋),而是加入队列尾部等待获取锁

    是

新请求锁的goroutine更容易获取锁的原因:
用官方话说就是,新请求锁的 Goroutine具有优势,它正在CPU上执行,而且可能有好几个,所以刚刚唤醒的 Goroutine 有很大可能在锁竞争中失败.

饥饿模式:

解决问题
主要解决了等待G队列的长尾问题(先进先出队列尾部的等待者一直无法获取到 mutex 的情况),防止G被饿死。但性能较低(由于新进入的活跃G起初处于自旋状态(消耗CPU资源),所以避免了G的切换调度)

执行过程
饥饿模式下,直接由unlock把锁交给等待队列中排在第一位的G,同时,饥饿模式下,新进来的G不会参与抢锁也不会进入自旋状态(禁用自旋),会直接进入等待队列的尾部排队。

触发条件
(1) 当一个G等待锁时间超过1毫秒时,Mutex切换到饥饿模式

取消条件
(1) 当一个G获取到锁且在等待队列的末尾(等待队列的所有任务执行完了),那么Mutex切换回正常模式
(2) 这个G获取锁的等待时间在1ms内,那么Mutex切换回正常模式

Mutex结构

 

type Mutex struct {
    state int32  // 表示锁当前的状态
    sema  uint32 // 信号量 用于向处于Gwaitting的G发送信号
}
// 状态值:
sema是个信号量,用来唤醒goroutine,初始为0,用于判断是否有可用资源。没有则一直等待。
state是一个4字节(32位)的变量,由于4部分组成,是锁的本体
(1) 0位判断当前锁是否上锁(锁定标志位,0:未锁定 1:锁定)
(2) 1位判断当前锁是否是被其他goroutine唤醒的(唤醒标志位,0:未唤醒 1:唤醒)
(3) 2位判断当前锁是否处于饥饿状态
(4) 3-31位用于计算当前等待的goroutine数量

关于锁的使用建议:
1. 写业务时不能全局使用同一个 Mutex
2. 千万不要将要加锁和解锁分到两个以上 Goroutine 中进行(容易形成死锁)
3. Mutex 千万不能被复制(包括不能通过函数参数传递),否则会复制传参前锁的状态:已锁定 or 未锁定。很容易产生死锁,关键是编译器还发现不了这个 Deadlock~
4. 尽量避免使用 Mutex,如果非使用不可,尽量多声明一些 Mutex,采用取模分片的方式去使用其中一个 Mutex(分段锁)(尽量减小锁的颗粒度)

更多可参考大佬文章:
1. Golang 并发编程与同步原语
2. 这可能是最容易理解的 Go Mutex 源码剖析 (什么是 Goroutine 排队?)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值