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 排队?)

目录
相关文章
|
7月前
|
物联网 Go 网络性能优化
使用Go语言(Golang)可以实现MQTT协议的点对点(P2P)消息发送。MQTT协议本身支持多种消息收发模式
使用Go语言(Golang)可以实现MQTT协议的点对点(P2P)消息发送。MQTT协议本身支持多种消息收发模式【1月更文挑战第21天】【1月更文挑战第104篇】
499 1
|
4月前
|
缓存 NoSQL Go
通过 SingleFlight 模式学习 Go 并发编程
通过 SingleFlight 模式学习 Go 并发编程
|
18天前
|
Go 调度 开发者
探索Go语言中的并发模式:goroutine与channel
在本文中,我们将深入探讨Go语言中的核心并发特性——goroutine和channel。不同于传统的并发模型,Go语言的并发机制以其简洁性和高效性著称。本文将通过实际代码示例,展示如何利用goroutine实现轻量级的并发执行,以及如何通过channel安全地在goroutine之间传递数据。摘要部分将概述这些概念,并提示读者本文将提供哪些具体的技术洞见。
|
2月前
|
安全 Go 调度
探索Go语言的并发模式:协程与通道的协同作用
Go语言以其并发能力闻名于世,而协程(goroutine)和通道(channel)是实现并发的两大利器。本文将深入了解Go语言中协程的轻量级特性,探讨如何利用通道进行协程间的安全通信,并通过实际案例演示如何将这两者结合起来,构建高效且可靠的并发系统。
|
2月前
|
安全 Go 开发者
破译Go语言中的并发模式:从入门到精通
在这篇技术性文章中,我们将跳过常规的摘要模式,直接带你进入Go语言的并发世界。你将不会看到枯燥的介绍,而是一段代码的旅程,从Go的并发基础构建块(goroutine和channel)开始,到高级模式的实践应用,我们共同探索如何高效地使用Go来处理并发任务。准备好,让Go带你飞。
|
4月前
|
存储 Unix 测试技术
解释Go中常见的I/O模式
解释Go中常见的I/O模式
|
4月前
|
存储 人工智能 算法
深入理解 go Mutex
深入理解 go Mutex
29 0
|
5月前
|
设计模式 Go
Go语言设计模式:使用Option模式简化类的初始化
在Go语言中,面对构造函数参数过多导致的复杂性问题,可以采用Option模式。Option模式通过函数选项提供灵活的配置,增强了构造函数的可读性和可扩展性。以`Foo`为例,通过定义如`WithName`、`WithAge`、`WithDB`等设置器函数,调用者可以选择性地传递所需参数,避免了记忆参数顺序和类型。这种模式提升了代码的维护性和灵活性,特别是在处理多配置场景时。
76 8
|
4月前
|
缓存 算法 Go