Go 并发可视化解释 - sync.Mute
在学习 Go 编程语言时,您可能会遇到这句著名的格言:“不要通过共享内存来进行通信;相反,通过通信来共享内存。” 这句话构成了 Go 强大并发模型的基础,其中通道(channels)作为协程之间的主要通信工具。然而,虽然通道是管理并发的多功能工具,但错误地假设我们应该始终用通道替换传统的锁定机制,如 Mutex,是一个错误的观念。在某些情况下,使用 Mutex 不仅恰当,而且比通道更有效。
在我的 Go 并发可视化系列中,今天我将通过视觉方式来解释 sync.Mutex。
Golang 基础
场景
想象一下,有四位 Gopher 自行车手每天骑车上班。他们都需要在到达办公室后洗个澡,但办公室只有一个浴室。为了防止混乱,他们确保一次只能有一个人使用浴室。这种独占式访问的概念正是 Go Mutex(互斥锁)的核心。
每天早上在办公室洗澡对自行车手和跑步者来说是一个小小的竞争。
普通模式
今天最早到达的是 Stringer。当他来的时候,没有人在使用浴室,因此他可以立即使用浴室。
对一个未加锁的 Mutex 调用 Lock() 会立即成功。
片刻后,Partier 到了。Partier 发现有人在使用浴室,但他不知道是谁,也不知道什么时候会结束使用。此时,他有两个选择:站在浴室前面(主动等待),或者离开并稍后再回来(被动等待)。按 Go 的术语,前者被称为“自旋”(spinning)。自旋的协程会占用 CPU 资源,增加了在锁定可用时获取 Mutex 的机会,而无需进行昂贵的上下文切换。然而,如果 Mutex 不太可能很快可用,继续占用 CPU 资源会降低其他协程获取 CPU 时间的机会。
从版本 1.21 开始,Golang 允许到达的协程自旋一段时间。如果在指定时间内无法获取 Mutex,它将进入休眠状态,以便其他协程有机会运行。
到达的协程首先自旋,然后休眠。
Candier 到了。就像 Partier 一样,她试图获取浴室。
因为她刚到,如果 Stringer 很快释放浴室,她就有很大的机会在被动等待之前获取它。这被称为普通模式。
普通模式的性能要好得多,因为协程可以连续多次获取 Mutex,即使有阻塞的等待者。
go/src/sync/mutex.go at go1.21.0 · golang/go · GitHub[1]
新到达的协程在争夺所有权时具有优势
饥饿模式
Partier 回来了。由于他等待的时间很长(超过 1 毫秒),他将尝试以饥饿模式获取浴室。当 Swimmer 来时,他注意到有人饿了,他不会尝试获取浴室,也不会自旋。相反,他会排队在等待队列的尾部。
在这种饥饿模式下,当 Candier 结束时,她会直接把浴室交给 Partier。此时没有竞争。
饥饿模式是防止尾延迟的病理情况的重要措施。
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 编程语言时,您可能会遇到这句著名的格言:“不要通过共享内存来进行通信;相反,通过通信来共享内存。” 这句话构成了 Go 强大并发模型的基础,其中通道(channels)作为协程之间的主要通信…...
十几张高清世界地图
十几张高清世界地图 仅供学习!...
Python 逢七拍手游戏
"""逢七拍手游戏介绍:逢七拍手游戏的规则是:从1开始顺序数数,数到有7,或者是7的倍数时,就拍一手。例如:7、14、17......70......知识点:1、循环语句for2、嵌套条件语句if/elif/e…...
Windows安装Mysql--免安装版
在Windows系统上安装免安装版MySql的步骤 官方下载地址:https://dev.mysql.com/downloads/mysql/ 将下载好的文件“mysql-5.7.18-winx64”解压缩到C盘的 目录下: 配置环境变量: (略) 正式安装,添加my.i…...
TypeScript中常见的操作符运算符总结
一、非空断言操作符(!) 当我们⽆法断定类型时,可以使用后缀表达式操作符 ! 来断⾔操作对象是⾮ null 或⾮ undefined 类型。 具体来说,比如表达式: x ! , 结果将从 x 值域中排除 null 和 unde…...
什么是泛型约束?
泛型约束(Generic Constraints)是一种在使用泛型时限制可接受类型的方式。它允许我们对泛型类型参数进行限定,以确保只有符合特定条件的类型才能被使用。 泛型约束的作用是提供更精确的类型控制和更强的类型安全性。通过约束泛型类型参数&am…...
代码随想录算法训练营 动态规划part11
一、买卖股票的最佳时机III 123. 买卖股票的最佳时机 III - 力扣(LeetCode) 请选一个喜欢的吧/(ㄒoㄒ)/~~123. 买卖股票的最佳时机 III - 力扣(LeetCode) class Solution {public int maxProfit(int[] prices) {if(pricesnul…...
新概念英语(第二册)复习——Lesson 16 - Lesson20
前言 新概念英语的16-20课,从21课开始,每天一课的速度更新,方便你能快速跟上。 文章目录 前言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…...
十小时开源了一个加密算法仓库,功能强大,后端开发人员狂喜!
写在前面 昨晚上睡觉前我就在想能不能把多个加密算法集成到一个库中,方便开发者调用,说干就干,今天肝了一天,中午直接吃的外卖哈哈哈哈,终于把仓库开源了,欢迎各位Go开发者Star和Fork! 仓库地址 go-cryp…...
标准化套利的使用
交易对象:目前使用郑商所,大商所的spd标准化套利组合进行交易。 交易平台:易盛极星极星产品网 手续费研究:白糖期货手续费和保证金2023年09月更新 - 九期网 本人使用的期货交易公司:中信期货(幸亏资金量大ÿ…...
【MySQL数据库事务操作、主从复制及Redis数据库读写分离、主从同步的实现机制】
文章目录 MySQL数据库事务操作、主从复制及Redis数据库读写分离、主从同步的实现机制ACID及如何实现事务隔离级别:MVCC 多版本并发控制MySQL数据库主从复制主从同步延迟怎么处理Redis 读写分离1.什么是主从复制2.读写分离的优点 Redis为什么快呢? MySQL数…...
十五、红外遥控器
十五、红外遥控器 介绍基本接收和发送遥控器键码外部中断和外部中断寄存器 红外解码中断函数红外遥控电机模块电机调速 介绍 基本接收和发送 空闲状态:红外LED不亮,接收头输出高电平发送低电平:红外LED以38KHz闪烁,接收头输出低…...
diot函数解析
文章目录 前言一、Rio_readinitb二、Rio_readlineb三、strstr四、strcat五、Open_clientfd六、Rio_writen总结 前言 备战CSAPP中的ProxyLab时解析书上的diot函数中遇到了一些不会的函数,遂解析记录。 一、Rio_readinitb 读和解析请求行 Rio_readinitb(&rio,…...
Python函数绘图与高等代数互融实例(一):正弦函数与余弦函数
Python函数绘图与高等代数互融实例(一):正弦函数与余弦函数 Python函数绘图与高等代数互融实例(二):闪点函数 Python函数绘图与高等代数互融实例(三):设置X|Y轴|网格线 Python函数绘图与高等代数互融实例(四):设置X|Y轴参考线|参考区域 Python函数绘图与高等代数互融实例(五…...
Python 判断回文数
"""判断输入的数是否为回文数介绍:回文数:数字从高位到低位正序排列和低位到高位逆序排列都是同一数值例如:数字 1221 无论正序还是逆序都是 1221知识点:1、获取字符串长度函数len()2、条件语句if/elif/else3、循环…...
人工智能在金融领域的五个应用案例
随着科技的进步,人工智能(Artificial Intelligence,AI)正逐渐渗透到各个行业中,其中包括金融领域。本文介绍人工智能在金融领域的五个应用案例,以期帮助大家更好地了解这个新兴技术在金融中的价值和作用。 文章目录 Part1 风险管理…...
java 工程管理系统源码+项目说明+功能描述+前后端分离 + 二次开发
Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下: 首页 工作台:待办工作、消息通知、预警信息,点击可进入相应的列表 项目进度图表:选择(总体或单个)项目显示…...
Effective C++看书笔记(2):构造/析构/赋值运算
构造/析构/赋值运算 5:了解C默默编写并调用哪些函数6:如果不想使用编译器自动生成的函数,就该明确拒绝7:为多态基类声明virtual析构函数8:别让异常逃离析构函数9:绝不在构造和析构过程中调用virtual函数10&…...
交换奇偶位:交换一个整数的二进制的奇偶位置(仅考虑正数情况)
方法二: 设计思想: 0xAAAAAAAA 的二进制表示为 10101010...(从最低位开始) 0x55555555 的二进制表示为 01010101...(从最低位开始) 问题:更加想不到掩码!!…...
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...
