Go Mutex 饥饿模式

简介: 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结构

image.gif编辑

 

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数量

image.gif

关于锁的使用建议:

1. 写业务时不能全局使用同一个 Mutex

2. 千万不要将要加锁和解锁分到两个以上 Goroutine 中进行(容易形成死锁)

3. Mutex 千万不能被复制(包括不能通过函数参数传递),否则会复制传参前锁的状态:已锁定 or 未锁定。很容易产生死锁,关键是编译器还发现不了这个 Deadlock~

4. 尽量避免使用 Mutex,如果非使用不可,尽量多声明一些 Mutex,采用取模分片的方式去使用其中一个 Mutex(分段锁)(尽量减小锁的颗粒度)

更多可参考大佬文章:

1. Golang 并发编程与同步原语

2. 这可能是最容易理解的 Go Mutex 源码剖析 (什么是 Goroutine 排队?)

目录
相关文章
|
5月前
|
物联网 Go 网络性能优化
使用Go语言(Golang)可以实现MQTT协议的点对点(P2P)消息发送。MQTT协议本身支持多种消息收发模式
使用Go语言(Golang)可以实现MQTT协议的点对点(P2P)消息发送。MQTT协议本身支持多种消息收发模式【1月更文挑战第21天】【1月更文挑战第104篇】
416 1
|
2月前
|
缓存 NoSQL Go
通过 SingleFlight 模式学习 Go 并发编程
通过 SingleFlight 模式学习 Go 并发编程
|
2月前
|
存储 Unix 测试技术
解释Go中常见的I/O模式
解释Go中常见的I/O模式
|
2月前
|
存储 人工智能 算法
深入理解 go Mutex
深入理解 go Mutex
15 0
|
3月前
|
设计模式 Go
Go语言设计模式:使用Option模式简化类的初始化
在Go语言中,面对构造函数参数过多导致的复杂性问题,可以采用Option模式。Option模式通过函数选项提供灵活的配置,增强了构造函数的可读性和可扩展性。以`Foo`为例,通过定义如`WithName`、`WithAge`、`WithDB`等设置器函数,调用者可以选择性地传递所需参数,避免了记忆参数顺序和类型。这种模式提升了代码的维护性和灵活性,特别是在处理多配置场景时。
62 8
|
2月前
|
缓存 算法 Go
|
2月前
|
测试技术 Go
|
2月前
|
存储 Unix 测试技术
解释Go中常见的I/O模式
解释Go中常见的I/O模式
|
2月前
|
设计模式 Go
下一篇
无影云桌面