当前位置: 首页 > news >正文

Go 并发可视化解释 - sync.Mute

在学习 Go 编程语言时,您可能会遇到这句著名的格言:“不要通过共享内存来进行通信;相反,通过通信来共享内存。” 这句话构成了 Go 强大并发模型的基础,其中通道(channels)作为协程之间的主要通信工具。然而,虽然通道是管理并发的多功能工具,但错误地假设我们应该始终用通道替换传统的锁定机制,如 Mutex,是一个错误的观念。在某些情况下,使用 Mutex 不仅恰当,而且比通道更有效。

在我的 Go 并发可视化系列中,今天我将通过视觉方式来解释 sync.Mutex

Golang 基础

场景

想象一下,有四位 Gopher 自行车手每天骑车上班。他们都需要在到达办公室后洗个澡,但办公室只有一个浴室。为了防止混乱,他们确保一次只能有一个人使用浴室。这种独占式访问的概念正是 Go Mutex(互斥锁)的核心。

bf34c38a3f6fc9ab2d159ffe05b90bbd.png

每天早上在办公室洗澡对自行车手和跑步者来说是一个小小的竞争。

普通模式

今天最早到达的是 Stringer。当他来的时候,没有人在使用浴室,因此他可以立即使用浴室。

对一个未加锁的 Mutex 调用 Lock() 会立即成功。

片刻后,Partier 到了。Partier 发现有人在使用浴室,但他不知道是谁,也不知道什么时候会结束使用。此时,他有两个选择:站在浴室前面(主动等待),或者离开并稍后再回来(被动等待)。按 Go 的术语,前者被称为“自旋”(spinning)。自旋的协程会占用 CPU 资源,增加了在锁定可用时获取 Mutex 的机会,而无需进行昂贵的上下文切换。然而,如果 Mutex 不太可能很快可用,继续占用 CPU 资源会降低其他协程获取 CPU 时间的机会。

从版本 1.21 开始,Golang 允许到达的协程自旋一段时间。如果在指定时间内无法获取 Mutex,它将进入休眠状态,以便其他协程有机会运行。

14dea1bc70160aedd59d555282387957.png

到达的协程首先自旋,然后休眠。

Candier 到了。就像 Partier 一样,她试图获取浴室。

1713dbf0667141489a7a37329b5932b3.png

因为她刚到,如果 Stringer 很快释放浴室,她就有很大的机会在被动等待之前获取它。这被称为普通模式。

普通模式的性能要好得多,因为协程可以连续多次获取 Mutex,即使有阻塞的等待者。

802c169f355683c6be50192d2c2b88c1.png
1*GJ7OW0_8z_8QjXPa2cFxPw.png

go/src/sync/mutex.go at go1.21.0 · golang/go · GitHub[1]

新到达的协程在争夺所有权时具有优势

饥饿模式

Partier 回来了。由于他等待的时间很长(超过 1 毫秒),他将尝试以饥饿模式获取浴室。当 Swimmer 来时,他注意到有人饿了,他不会尝试获取浴室,也不会自旋。相反,他会排队在等待队列的尾部。

在这种饥饿模式下,当 Candier 结束时,她会直接把浴室交给 Partier。此时没有竞争。

b37df75b60e60bc216fc9153faa06bc4.png

饥饿模式是防止尾延迟的病理情况的重要措施。

1411210b8c3edaa0fa92edf479dbc3a9.png
7d4dfe9465324ba15876a95a37811c01.png

Partier 完成了他的回合并释放了浴室。此时,只有 Swimmer 在等待,因此他将立即拥有它。Swimmer 如果发现自己是最后一个等待的人,他会将 Mutex 设置回普通模式。如果他发现自己的等待时间少于 1 毫秒,也会这样做。

最后,Swimmer 在使用浴室后释放了它。请注意,Mutex 不会将所有者从“已锁定(由 Goroutine A 锁定)”状态更改为“已锁定(由 Goroutine B 锁定)”状态。它始终会在“已锁定”到“未锁定”然后再到“已锁定”的状态之间切换。出于简洁起见,上面的图像中省略了中间状态。

展示代码!

Mutex 的实现随时间而变化,实际上,要完全理解它的实现并不容易。幸运的是,我们不必完全理解其实现就能高效使用它。如果从这篇博客中只能记住一件事,那一定是:早到的人不一定赢得比赛。相反,新到达的协程通常具有更高的机会,因为它们仍在 CPU 上运行。Golang 还尝试避免通过实现饥饿模式来使等待者被饿死。

package mainimport ("fmt""sync""time"
)func main() {wg := sync.WaitGroup{}wg.Add(4)bathroom := sync.Mutex{}takeAShower := func(name string) {defer wg.Done()fmt.Printf("%s: I want to take a shower. I'm trying to acquire the bathroom\n", name)bathroom.Lock()fmt.Printf("%s: I have the bathroom now, taking a shower\n", name)time.Sleep(500 * time.Microsecond)fmt.Printf("%s: I'm done, I'm unlocking the bathroom\n", name)bathroom.Unlock()}go takeAShower("Partier")go takeAShower("Candier")go takeAShower("Stringer")go takeAShower("Swimmer")wg.Wait()fmt.Println("main: Everyone is Done. Shutting down...")
}

正如您可能猜到的,并发代码的结果几乎总是非确定性的。

第一次

Swimmer: I want to take a shower. I'm trying to acquire the bathroom

Partier: I want to take a shower. I'm trying to acquire the bathroom

Candier: I want to take a shower. I'm trying to acquire the bathroom

Stringer: I want to take a shower. I'm trying to acquire the bathroom

Swimmer: I have the bathroom now, taking a shower

Swimmer: I'm done, I'm unlocking the bathroom

Partier: I have the bathroom now, taking a shower

Partier: I'm done, I'm unlocking the bathroom

Candier: I have the bathroom now, taking a shower

Candier: I'm done, I'm unlocking the bathroom

Stringer: I have the bathroom now, taking a shower

Stringer: I'm done, I'm unlocking the bathroom

main: Everyone is Done. Shutting down...

第二次

Swimmer: I want to take a shower. I'm trying to acquire the bathroom

Swimmer: I have the bathroom now, taking a shower

Partier: I want to take a shower. I'm trying to acquire the bathroom

Stringer: I want to take a shower. I'm trying to acquire the bathroom

Candier: I want to take a shower. I'm trying to acquire the bathroom

Swimmer: I'm done, I'm unlocking the bathroom

Partier: I have the bathroom now, taking a shower

Partier: I'm done, I'm unlocking the bathroom

Stringer: I have the bathroom now, taking a shower

Stringer: I'm done, I'm unlocking the bathroom

Candier: I have the bathroom now, taking a shower

Candier: I'm done, I'm unlocking the bathroom

main: Everyone is Done. Shutting down...

自己实现 Mutex

实现 sync.Mutex 是困难的,但使用具有缓冲的通道来实现 Mutex 却相当容易。

type MyMutex struct {ch chan bool
}func NewMyMutex() *MyMutex {return &MyMutex{// 缓冲大小必须为 1ch: make(chan bool, 1),}
}// Lock 锁定 m。
// 如果锁已被使用,调用的协程将被阻塞,直到 Mutex 可用。
func (m *MyMutex) Lock() {[m.ch](http://m.ch) <- true
}// Unlock 解锁 m。
func (m *MyMutex) Unlock() {<-m.ch
}

这篇文章通过生动的场景和可视化效果很好地解释了 Go 语言中 sync.Mutex 的工作原理,以及如何使用互斥锁来管理并发

相关系列文章

使用通信顺序进程(CSP)模型的 Go 语言通道

Go并发可视化解释 – select语句

以可视化方式解释 Go 并发 - 通道

相关文章:

Go 并发可视化解释 - sync.Mute

在学习 Go 编程语言时&#xff0c;您可能会遇到这句著名的格言&#xff1a;“不要通过共享内存来进行通信&#xff1b;相反&#xff0c;通过通信来共享内存。” 这句话构成了 Go 强大并发模型的基础&#xff0c;其中通道&#xff08;channels&#xff09;作为协程之间的主要通信…...

十几张高清世界地图

十几张高清世界地图 仅供学习&#xff01;...

Python 逢七拍手游戏

"""逢七拍手游戏介绍&#xff1a;逢七拍手游戏的规则是&#xff1a;从1开始顺序数数&#xff0c;数到有7&#xff0c;或者是7的倍数时&#xff0c;就拍一手。例如&#xff1a;7、14、17......70......知识点&#xff1a;1、循环语句for2、嵌套条件语句if/elif/e…...

Windows安装Mysql--免安装版

在Windows系统上安装免安装版MySql的步骤 官方下载地址&#xff1a;https://dev.mysql.com/downloads/mysql/ 将下载好的文件“mysql-5.7.18-winx64”解压缩到C盘的 目录下&#xff1a; 配置环境变量&#xff1a; &#xff08;略&#xff09; 正式安装&#xff0c;添加my.i…...

TypeScript中常见的操作符运算符总结

一、非空断言操作符&#xff08;!&#xff09; 当我们⽆法断定类型时&#xff0c;可以使用后缀表达式操作符 &#xff01; 来断⾔操作对象是⾮ null 或⾮ undefined 类型。 具体来说&#xff0c;比如表达式&#xff1a; x ! &#xff0c; 结果将从 x 值域中排除 null 和 unde…...

什么是泛型约束?

泛型约束&#xff08;Generic Constraints&#xff09;是一种在使用泛型时限制可接受类型的方式。它允许我们对泛型类型参数进行限定&#xff0c;以确保只有符合特定条件的类型才能被使用。 泛型约束的作用是提供更精确的类型控制和更强的类型安全性。通过约束泛型类型参数&am…...

代码随想录算法训练营 动态规划part11

一、买卖股票的最佳时机III 123. 买卖股票的最佳时机 III - 力扣&#xff08;LeetCode&#xff09; 请选一个喜欢的吧/(ㄒoㄒ)/~~123. 买卖股票的最佳时机 III - 力扣&#xff08;LeetCode&#xff09; class Solution {public int maxProfit(int[] prices) {if(pricesnul…...

新概念英语(第二册)复习——Lesson 16 - Lesson20

前言 新概念英语的16-20课&#xff0c;从21课开始&#xff0c;每天一课的速度更新&#xff0c;方便你能快速跟上。 文章目录 前言Lesson 16 - A polite request原文译文单词 Lesson 17 - Always Young原文译文单词 Lesson 18 - He often does this!原文译文单词Lesson 19 - So…...

[题] n-皇后问题 #深搜 #DFS

题目 AcWing 843. n-皇后问题 代码 #include<bits/stdc.h> using namespace std; const int N 20; int n, p[N]; char g[N][N]; bool col[N], dg[N], udg[N]; void D (int u){if(u n){for(int j 0; j < n; j )puts(g[j]);cout << endl;return ;}for(int i…...

十小时开源了一个加密算法仓库,功能强大,后端开发人员狂喜!

写在前面 昨晚上睡觉前我就在想能不能把多个加密算法集成到一个库中&#xff0c;方便开发者调用&#xff0c;说干就干&#xff0c;今天肝了一天&#xff0c;中午直接吃的外卖哈哈哈哈&#xff0c;终于把仓库开源了&#xff0c;欢迎各位Go开发者Star和Fork! 仓库地址 go-cryp…...

标准化套利的使用

交易对象&#xff1a;目前使用郑商所&#xff0c;大商所的spd标准化套利组合进行交易。 交易平台&#xff1a;易盛极星极星产品网 手续费研究:白糖期货手续费和保证金2023年09月更新 - 九期网 本人使用的期货交易公司&#xff1a;中信期货&#xff08;幸亏资金量大&#xff…...

【MySQL数据库事务操作、主从复制及Redis数据库读写分离、主从同步的实现机制】

文章目录 MySQL数据库事务操作、主从复制及Redis数据库读写分离、主从同步的实现机制ACID及如何实现事务隔离级别&#xff1a;MVCC 多版本并发控制MySQL数据库主从复制主从同步延迟怎么处理Redis 读写分离1.什么是主从复制2.读写分离的优点 Redis为什么快呢&#xff1f; MySQL数…...

十五、红外遥控器

十五、红外遥控器 介绍基本接收和发送遥控器键码外部中断和外部中断寄存器 红外解码中断函数红外遥控电机模块电机调速 介绍 基本接收和发送 空闲状态&#xff1a;红外LED不亮&#xff0c;接收头输出高电平发送低电平&#xff1a;红外LED以38KHz闪烁&#xff0c;接收头输出低…...

diot函数解析

文章目录 前言一、Rio_readinitb二、Rio_readlineb三、strstr四、strcat五、Open_clientfd六、Rio_writen总结 前言 备战CSAPP中的ProxyLab时解析书上的diot函数中遇到了一些不会的函数&#xff0c;遂解析记录。 一、Rio_readinitb 读和解析请求行 Rio_readinitb(&rio,…...

Python函数绘图与高等代数互融实例(一):正弦函数与余弦函数

Python函数绘图与高等代数互融实例(一):正弦函数与余弦函数 Python函数绘图与高等代数互融实例(二):闪点函数 Python函数绘图与高等代数互融实例(三):设置X|Y轴|网格线 Python函数绘图与高等代数互融实例(四):设置X|Y轴参考线|参考区域 Python函数绘图与高等代数互融实例(五…...

Python 判断回文数

"""判断输入的数是否为回文数介绍&#xff1a;回文数&#xff1a;数字从高位到低位正序排列和低位到高位逆序排列都是同一数值例如&#xff1a;数字 1221 无论正序还是逆序都是 1221知识点&#xff1a;1、获取字符串长度函数len()2、条件语句if/elif/else3、循环…...

人工智能在金融领域的五个应用案例

随着科技的进步&#xff0c;人工智能(Artificial Intelligence&#xff0c;AI)正逐渐渗透到各个行业中&#xff0c;其中包括金融领域。本文介绍人工智能在金融领域的五个应用案例&#xff0c;以期帮助大家更好地了解这个新兴技术在金融中的价值和作用。 文章目录 Part1 风险管理…...

java 工程管理系统源码+项目说明+功能描述+前后端分离 + 二次开发

Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下&#xff1a; 首页 工作台&#xff1a;待办工作、消息通知、预警信息&#xff0c;点击可进入相应的列表 项目进度图表&#xff1a;选择&#xff08;总体或单个&#xff09;项目显示…...

Effective C++看书笔记(2):构造/析构/赋值运算

构造/析构/赋值运算 5&#xff1a;了解C默默编写并调用哪些函数6&#xff1a;如果不想使用编译器自动生成的函数&#xff0c;就该明确拒绝7&#xff1a;为多态基类声明virtual析构函数8&#xff1a;别让异常逃离析构函数9&#xff1a;绝不在构造和析构过程中调用virtual函数10&…...

交换奇偶位:交换一个整数的二进制的奇偶位置(仅考虑正数情况)

方法二&#xff1a; 设计思想&#xff1a; 0xAAAAAAAA 的二进制表示为 10101010...&#xff08;从最低位开始&#xff09; 0x55555555 的二进制表示为 01010101...&#xff08;从最低位开始&#xff09; 问题&#xff1a;更加想不到掩码&#xff01;&#xff01;&#xf…...

手游刚开服就被攻击怎么办?如何防御DDoS?

开服初期是手游最脆弱的阶段&#xff0c;极易成为DDoS攻击的目标。一旦遭遇攻击&#xff0c;可能导致服务器瘫痪、玩家流失&#xff0c;甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案&#xff0c;帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

基于大模型的 UI 自动化系统

基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法

树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作&#xff0c;无需更改相机配置。但是&#xff0c;一…...

椭圆曲线密码学(ECC)

一、ECC算法概述 椭圆曲线密码学&#xff08;Elliptic Curve Cryptography&#xff09;是基于椭圆曲线数学理论的公钥密码系统&#xff0c;由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA&#xff0c;ECC在相同安全强度下密钥更短&#xff08;256位ECC ≈ 3072位RSA…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案

随着新能源汽车的快速普及&#xff0c;充电桩作为核心配套设施&#xff0c;其安全性与可靠性备受关注。然而&#xff0c;在高温、高负荷运行环境下&#xff0c;充电桩的散热问题与消防安全隐患日益凸显&#xff0c;成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

CMake 从 GitHub 下载第三方库并使用

有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

.Net Framework 4/C# 关键字(非常用,持续更新...)

一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域&#xff0c;Hive 作为 Hadoop 生态中重要的数据仓库工具&#xff0c;其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式&#xff0c;很多开发者常常陷入选择困境。本文将从底…...