go语言并发编程-超详细mutex解析
文章目录
- 1 go语言并发编程学习-mutex
- 1.1 学习过程
- 1.2 如何解决资源并发访问的问题?【基本用法】
- 1.2.1 并发访问带来的问题
- 1.2.1.1 导致问题的原因
- 1.2.2 race detector检查data race
- 1.2.3 mutex的基本实现机制以及使用方法
- 1.2.3.1 具体使用-1
- 1.2.3.1 具体使用-2
1 go语言并发编程学习-mutex
1.1 学习过程

1.2 如何解决资源并发访问的问题?【基本用法】
本小节主要为了解答以下问题:
- 为什么需要解决并发访问的问题?
- 怎么通过race detector来查找程序中的data race?
- mutex的基本机制和基本使用方法?
1.2.1 并发访问带来的问题
1. 多个goroutine并发更新计数器
在多个goroutine的情况下并发更新计数器,得到的值可能不符合预期。
package mainimport ("fmt""sync"
)var counter int
var wg sync.WaitGroupfunc increment() {defer wg.Done()counter += 100
}func main() {wg.Add(1000) // 这个可以先不管,理解为,main函数需要等待goroutine都执行完才能退出,可以把wg相关全去掉,在main函数后面加上time.sleep(time.second * 10)for i := 0; i < 1000; i++ {go increment()}wg.Wait()fmt.Println("Final counter:", counter) // 期望输出 2,但可能输出 0 或 1
}
2. 更新用户的账户余额
在用户收入和支出的时候,如果不同的goroutine同时对该账户余额进行更新处理的时候,可能会导致余额错误
package mainimport ("fmt""sync"
)var balance int = 1000
var wg sync.WaitGroupfunc deposit(amount int) {defer wg.Done()balance += amount
}func withdraw(amount int) {defer wg.Done()balance -= amount
}func main() {wg.Add(2000)for i := 0; i < 1000; i++ {go deposit(1)go withdraw(1)}wg.Wait()fmt.Println("Final balance:", balance) // 期望输出 1000,但可能输出其他值
}
3. 秒杀系统
没有互斥锁的情况下,可能会出现超卖的情况。也就是商品已经没有了,但是还是可以进行出售商品,商品数量减1的操作。
package mainimport ("fmt""sync"
)var stock int = 10
var wg sync.WaitGroupfunc purchase() {defer wg.Done()if stock > 0 {fmt.Println("Stock:", stock)stock--}
}func main() {wg.Add(100000)for i := 0; i < 100000; i++ {go purchase()}wg.Wait()fmt.Println("Final stock:", stock) // 期望输出 0,但可能输出负数
}
还有一些其他的场景,比如并发写入buffer等等,不解决并发访问的问题,就会发生很严重的后果。
1.2.1.1 导致问题的原因
并发访问问题的核心在于对共享资源的非原子性操作。临界区是指一段需要独占访问的代码块,多个goroutine在执行这段代码时,如果没有同步机制(如互斥锁)来保证互斥访问,就可能会产生数据竞争,导致数据不一致和其他问题。以下从临界区的角度来解释这些问题。下面分析多个goroutine并发更新计数器:
计数器的更新操作通常包括以下步骤:
- 读取当前计数器的值
- 对读取的值进行加法运算
- 将结果写回计数器
在并发情况下,如果两个goroutine同时执行这三个步骤中的任意一个步骤,没有同步机制来保证这三个步骤是原子操作,就会产生问题:
Goroutine 1: 读取 counter = 0
Goroutine 2: 读取 counter = 0
Goroutine 1: counter = 0 + 1 => counter = 1
Goroutine 2: counter = 0 + 1 => counter = 1 (覆盖了Goroutine 1的结果)
那么怎么在程序运行的时候发现呢?可以参考一下race detector工具
1.2.2 race detector检查data race
可以使用上文的秒杀系统作为例子。写这个的时候,图片转存失败,因此决定用极客上的图片。
1、 在执行go run counter.go的时候会出现以下结果,是可以正常运行通过的,但是结果不如愿:

2、但是假如race之后:go run -race main.go

这个警告不但会告诉你有并发问题,而且还会告诉你哪个goroutiine在哪一行对哪个变量有写操作,同时,哪个goroutine在哪一行对哪个变量有读操作,就是这些并发的读写访问,引起了datarace。
例子中的goroutine 10 对内存地址0x000126010有读的操作(ctounter.go文件第16行),同时,goroutine7对内存地址0x00c000126010有写的操作(counter.go文件第16行)。而且还可能有多个goroutine在同时进行读写,所以,警告信息可能会很长。
总结一下,通过在编译的时候插入一些指令,在运行时通过这些插入的指令检测并发读写从而发现data race问题,就是这个工具的实现机制。
既然这个例子存在data race问题,我们就要想办法来解决它。这个时候,我们这节课的主角Mutex就要登场了,它可以轻松地消除掉data race。
具体怎么做呢?下面,我就结合这个例子,来具体说一说Mutex的基本用法。
1.2.3 mutex的基本实现机制以及使用方法

mutex的基本实现的机制,就是每次只允许一个goroutine进入临界区,具体就是进入临界区的时候给临界区加上一个锁,禁止其他goroutine进入临界区,在退出临界区的时候释放锁,从而允许其他goroutine进入。
Mutex 是 Go 语言中常用的同步原语,用于控制对共享资源的独占访问。Mutex 实现了 sync.Locker 接口,该接口定义了两个方法:Lock 和 Unlock。在解释 Mutex 的基本使用方法之前,先简单介绍一下 sync.Locker 接口:
type Locker interface { Lock() Unlock()
}
任何实现了 Lock 和 Unlock 方法的类型,都可以被视为 Locker,所以 sync.Mutex 也实现了这个接口。以下是一个基本的 sync.Mutex 的使用方法:
1、基本使用方法:
声明一个sync.Mutex类型的变量,无需初始化
var mutex sync.Mutex
2、在需要保护的临界区前调用 Lock 方法:
mutex.Lock()
3、在临界区结束后调用 Unlock 方法:
mutex.Unlock()
1.2.3.1 具体使用-1
这种使用,主要是在临界区代码中直接使用mutex即可。
package mainimport ("fmt""sync"
)var stock int = 10
var wg sync.WaitGroup
var mutex sync.Mutexfunc purchase() {defer wg.Done()mutex.Lock() // 加锁,进入临界区defer mutex.Unlock() // 确保解锁if stock > 0 {fmt.Println("Stock:", stock)stock--}
}func main() {wg.Add(100000)for i := 0; i < 100000; i++ {go purchase()}wg.Wait()fmt.Println("Final stock:", stock) // 期望输出 0,但可能输出负数
}
1.2.3.1 具体使用-2
该使用是把mutex和临界区资源封装为一个类,这样更好的进行复用,不暴露内部实现
package mainimport ("fmt""sync"
)// StockManager 结构体封装了库存和互斥锁
type StockManager struct {stock intmutex sync.Mutex
}// NewStockManager 创建一个新的 StockManager
func NewStockManager(initialStock int) *StockManager {return &StockManager{stock: initialStock}
}// Purchase 尝试购买一个商品
func (sm *StockManager) Purchase() bool {sm.mutex.Lock()defer sm.mutex.Unlock()if sm.stock > 0 {sm.stock--fmt.Println("Purchase successful, remaining stock:", sm.stock)return true}fmt.Println("Purchase failed, out of stock")return false
}// GetStock 获取当前库存
func (sm *StockManager) GetStock() int {sm.mutex.Lock()defer sm.mutex.Unlock()return sm.stock
}func main() {sm := NewStockManager(10)var wg sync.WaitGroupnumUsers := 100000wg.Add(numUsers)for i := 0; i < numUsers; i++ {go func() {defer wg.Done()sm.Purchase()}()}wg.Wait()fmt.Println("Final stock:", sm.GetStock())
}
下一篇:mutex的原理以及常见的错误。
相关文章:
go语言并发编程-超详细mutex解析
文章目录 1 go语言并发编程学习-mutex1.1 学习过程1.2 如何解决资源并发访问的问题?【基本用法】1.2.1 并发访问带来的问题1.2.1.1 导致问题的原因 1.2.2 race detector检查data race1.2.3 mutex的基本实现机制以及使用方法1.2.3.1 具体使用-11.2.3.1 具体使用-2 1 …...
VirtualBox Debian 自动安装脚本
概览 相较于原脚本(安装目录/UnattendedTemplates/debian_pressed.cfg)更新如下内容: 配置清华镜像源配置仅主机网卡(后续只需添加仅主机网卡即可)配置Root用户远程登录配置用户sudo组 脚本 debian_pressed.cfg ##…...
最好的开放式耳机?五款红榜开放式耳机推荐!
面对众多的开放式耳机选项,消费者可能会感到难以抉择。买耳机不一定要买最贵最好的,但是一定要选最适合自己的,为了使选择过程更加容易,我提供了一些建议,推荐了几款既适合日常使用又佩戴舒适的热门开放式耳机。 开放式…...
线性代数之线性方程组
目录 线性方程组 1. 解的个数 齐次线性方程组: 非齐次线性方程组: 2. 齐次线性方程组的解 3. 非齐次线性方程组的解 4. 使用 Python 和 NumPy 求解线性方程组 示例代码 齐次线性方程组 非齐次线性方程组 示例结果 齐次线性方程组 非齐次线性…...
速盾:怎么查看是否使用cdn服务?
CDN(Content Delivery Network),即内容分发网络,是一种加速网络内容传输的技术。通过在全球各地建立分布式的节点服务器,将网站的静态资源缓存到最近的节点服务器上,使用户可以从离自己地理位置最近的节点服…...
828华为云征文|采用Flexus云服务器X实例部署RTSP直播服务器
一、前言 这篇文章讲解: 采用华为云最新推出的Flexus云服务器X实例搭建RTSP服务器,完成视频直播需求。 随着实时视频流传输需求的增长,RTSP(实时流协议)服务器成为了许多视频监控、直播和多媒体应用的核心组件。在当…...
Spring Cloud Gateway(二)
Spring Cloud Gateway(二) 文章目录 Spring Cloud Gateway(二)Gateway工作原理为什么使用API网关高并发Gateway性能优化 Gateway工作原理 Spring Cloud Gateway旨在为微服务架构提供简单、有效并且统一的API路由管理方式。它不仅…...
docker 简易入门
# docker 简易入门 docker由几个组成部分 docker client: 即 docker 命令行工具 docker host: 宿主机,docker daemon 的运行环境服务器 docker daemon: docker 的守护进程,docker client 通过命令行与 docker daemon 交互 container: 最小型的一个操…...
【看雪-注册安全分析报告】
前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞…...
记录一个前端学习小组的收集的模版
问题1:输入“您的姓名”,选择“短答案”作为问题类型。问题2:输入“您是否愿意继续参加前端学习小组?”,选择“单选”作为问题类型,并添加选项“是”和“否”。问题3:输入“如果您选择‘是’&am…...
Rk3588 Android12 AIDL 开发
AIDL (Android Interface Definition Language) 和 HIDL (HAL Interface Definition Language) 都是 Android 系统中用于定义接口的工具,但它们有不同的用途和特性。 AIDL (Android Interface Definition Language) 用途: 主要用于应用程序之间的进程间…...
两个长整数字符串求和(不允许使用ES6+)
两个长整数字符串求和(不允许使用ES6), 面试手撸代码遇到到这个问题 1. 实现方式第一种 // 短整数字符串前边补 0; num需要补 0 的短整数字符串, len 长整数字符串的长度 function fillZero (num, len) {let str num.toString();if (str.length < len) {str 0.repeat(…...
11 Java 方法引用、异常处理、Java接口之函数式编程(接口知识补充Function<T,R>、BiFunction<T, U, R>和自定义泛型接口)
文章目录 前言一、Java接口之函数式编程 --- 接口知识补充1 Function<T,R>泛型接口2 BiFunction<T, U, R>泛型接口3 自定义泛型函数式编程接口4 使用lambda表达式、方法引用进行函数式编程二、方法引用1 方法引用初体验(以Array.sort()方法为例)(1)什么是方法引…...
深入探索 Go 语言的编译器与垃圾回收机制
Go 编译器 Go 编译器是通过 go 工具执行的,这个工具的功能不仅仅是生成可执行文件。你可以使用 go tool compile 命令来编译一个 Go 源文件。这个操作将生成一个目标文件,也就是 .o 后缀的文件。以下是在 macOS Mojave 系统上执行的命令和结果展示&…...
2024国赛数学建模-模拟火算法(MATLAB 实现)
模拟退火算法 1.1 算法原理 模拟退火算法的基本思想是从一给定解开始 ,从邻域 中随机产生另一个解 ,接受 Metropolis准则允许目标函数在 有限范围内变坏 ,它由一控制参数 t决定 ,其作用类似于物 理过程中的温度 T,对于控制参数的每一取值 ,算法持续进 行“产生 —判断 —接受…...
YOLOv8 只检测人 只画框不要标签
参考了这个:YOLOv8只检测人(或其他一种或者多种类别)_yolov8只检测指定类别-CSDN博客 1. 只检测人:predict的时候指定参数classes[0] 2. 只画框不要标签:plot的时候传入labelsFalse 3. 标签中去掉置信度:…...
如何将网络安全防范游戏化
组织对威胁的准备和恢复能力跟不上网络犯罪分子的进步。 一些首席执行官仍然认为网络安全需要偶尔干预,而不是持续关注。 但对于许多公司来说,情况并非如此;网络威胁准备需要协调一致的培训工作,因此网络安全团队在攻击发生时已…...
Qt QGraphicsView实现图片放缩、鼠标拖动移动、鼠标点位置放大缩小_图片查看
QtQGraphicsView实现图片放缩、鼠标拖动移动、鼠标点位置放大缩小 头文件: #ifndef TIMGWIDGET_H #define TIMGWIDGET_H#include <QGraphicsItem> #include <QMainWindow> #include <QObject> #include <QWidget>// class TImgWidget : pu…...
Percona Toolkit 神器全攻略(复制类)
Percona Toolkit 神器全攻略(复制类) Percona Toolkit 神器全攻略系列共八篇,前文回顾: 前文回顾Percona Toolkit 神器全攻略Percona Toolkit 神器全攻略(实用类)Percona Toolkit 神器全攻略(配…...
SQLite3 数据类型深入全面讲解
SQLite3,作为一款轻量级的数据库管理系统,在数据存储方面展现出了其独特的魅力。它不仅支持标准的SQL语法,还提供了丰富的数据类型供开发者选择。这些数据类型不仅涵盖了基本的数值和文本类型,还包括了日期时间、二进制数据等复杂…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
企业如何增强终端安全?
在数字化转型加速的今天,企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机,到工厂里的物联网设备、智能传感器,这些终端构成了企业与外部世界连接的 “神经末梢”。然而,随着远程办公的常态化和设备接入的爆炸式…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
GitFlow 工作模式(详解)
今天再学项目的过程中遇到使用gitflow模式管理代码,因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存,无论是github还是gittee,都是一种基于git去保存代码的形式,这样保存代码…...
在 Spring Boot 中使用 JSP
jsp? 好多年没用了。重新整一下 还费了点时间,记录一下。 项目结构: pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...
