从汇编角度解释线程间互斥-mutex互斥锁与lock_guard的使用
多线程并发的竞态问题
我们创建三个线程同时进行购票,代码如下
#include<iostream>
#include<thread>
#include<list>
using namespace std;
//总票数
int ticketCount=100;
//售票线程
void sellTicket(int idx)
{while(ticketCount>0){cout<<ticketCount<<endl;ticketCount--;std::this_thread::sleep_for(std::chrono::milliseconds(100));//休眠100ms}
}int main()
{list<std::thread> tlist;//存储线程for(int i=1;i<=3;i++)//创建三个线程{tlist.push_back(std::thread(sellTicket,i));}for(auto& tl:tlist){tl.join();//让主线程等待子线程执行结束}return 0;
}

我们再看这段代码的汇编过程
ticketCount--;
汇编代码如下:
mov eax,ticketCount
sub eax,1
mov ticketCount,eax
上述汇编过程的解读为:
- 将ticketCount的值从内存放到寄存器eax
- 通过寄存器完成减法操作
- 将运算结果再从eax寄存器中放到内存中
可以看到,三个线程在执行代码时,每个线程在执行到ticketCount--时,在底层都会执行上述三行汇编代码,这种竞态必然会导致最终结果的错误。
如:
- 假如现在ticketCount的值为100
- 线程一把ticketCount的值从内存放到寄存器并完成了减法操作,则此时ticketCount的值为99,但并未将计算后的结果放到内存,也就是说此时内存中ticketCount的值仍旧为100
- 线程二开始执行代码,那么线程二从内存取出ticketCount的值放到eax寄存器时必然为100,因此线程二在进行计算后的结果也是99
- 之后线程一又开始继续执行代码,将他的计算结果99写回内存,则此时输出结果为99
- 切换到线程二继续执行代码,然而线程二的结果也是99
可以看到,本来两个线程在执行减法操作后,ticketCount的结果应该为98,但是现在的结果却都是99。
出现上述结果的原因就在于ticketCount--代码执行的汇编过程不是一次性完成的
mutex互斥锁
这就是互斥锁出现的作用——保证ticketCount--代码的汇编过程一次性执行
std::mutex是 C++11 引入的互斥量(Mutex)类,用于在多线程环境中实现互斥访问共享资源。通过
std::mutex,可以确保在同一时间只有一个线程可以访问被保护的临界区,从而避免多个线程同时对共享数据进行修改而导致的数据竞争问题。
std::mutex提供了lock()和unlock()方法,分别用于锁定和解锁互斥量。需要注意的是,在编写多线程程序时,必须确保每次lock()操作都会有对应的unlock()操作,以避免死锁等问题。
修改后的代码如下:
#include<iostream>
#include<thread>
#include<list>
#include<mutex>
using namespace std;
//总票数
int ticketCount=100;
std::mutex mtx;
//售票线程
void sellTicket(int idx)
{while(ticketCount>0){mtx.lock();//加锁cout<<ticketCount<<endl;ticketCount--;//解锁mtx.unlock();std::this_thread::sleep_for(std::chrono::milliseconds(100));//休眠100ms}
}int main()
{list<std::thread> tlist;//存储线程for(int i=1;i<=3;i++)//创建三个线程{tlist.push_back(std::thread(sellTicket,i));}for(auto& tl:tlist){tl.join();//让主线程等待子线程执行结束}return 0;
}
但是上述代码仍旧有一些问题,考虑以下情况
- 假如ticketCount的值为1
- 由于ticketCount大于0,因此线程一进入while循环并获取锁,但并未执行--操作,因此此时ticketCount的仍旧为1
- 假如此时线程二刚好被切换,那么由于此时ticketCount的值还没有变化,仍旧为1大于0,因此线程二也进入while循环,但是线程一并未释放锁,因此线程将被卡住
- 之后线程一继续执行,执行减法操作,ticketCount的值为0,并释放锁
- 此时线程二继续执行,但是线程二已经进入while循环了,因此线程二也将执行一次减法操作,故而就会出现ticketCount=-1的情况
因此修正后的代码应该是
void sellTicket(int idx)
{while(ticketCount>0){mtx.lock();if(ticketCount>0){cout<<ticketCount<<endl;ticketCount--;}mtx.unlock();std::this_thread::sleep_for(std::chrono::milliseconds(100));//休眠100ms}
}
lock_guard
由于mutex需要程序员时刻记住在何时加锁在何时释放锁,否则就会导致死锁问题,但大多数时候这个工作比较繁琐,并且很容易忘记释放锁,因此出现了lock_guard,可自动管理加锁和解锁
std::lock_guard是 C++11 提供的 RAII(资源获取即初始化)风格的锁管理工具,用于自动管理std::mutex的加锁和解锁操作。- 通过
std::lock_guard,可以在作用域内自动锁定std::mutex,并在作用域结束时自动释放锁,从而避免忘记手动解锁或异常情况下未能正确解锁互斥量。std::lock_guard的构造函数接受一个std::mutex对象,并在构造时锁定该互斥量,在析构时释放锁。因此,使用std::lock_guard可以很方便地实现线程安全的代码块。
void sellTicket(int idx)
{while(ticketCount>0){// mtx.lock();{lock_guard<mutex> lock(mtx);if(ticketCount>0){cout<<ticketCount<<endl;ticketCount--;}}// mtx.unlock();std::this_thread::sleep_for(std::chrono::milliseconds(100));//休眠100ms}
}
相关文章:
从汇编角度解释线程间互斥-mutex互斥锁与lock_guard的使用
多线程并发的竞态问题 我们创建三个线程同时进行购票,代码如下 #include<iostream> #include<thread> #include<list> using namespace std; //总票数 int ticketCount100; //售票线程 void sellTicket(int idx) {while(ticketCount>0){cou…...
高程 | 多态性(c++)
文章目录 📚多态📚运算符重载🐇定义🐇规则🐇友元运算符重载函数🐇成员运算符重载函数 📚虚函数📚纯虚函数和抽象类 📚多态 多态:同样的消息被不同类型的对象…...
LV.23 D2 开发环境搭建及平台介绍 学习笔记
一、Keil MDK-ARM简介及安装 Keil MDK,也称MDK-ARM,Realview MDK (Microcontroller Development Kit)等。目前Keil MDK 由三家国内代理商提供技术支持和相关服务。 MDK-ARM软件为基于Cortex-M、Cortex-R4、ARM7、ARM9处理器设备…...
[uniapp生命周期]详细讲解uniapp中那些属于vue生命周期,那些属于uniapp独有的生命周期,以及这中间的区别 相关的内容和api 代码注释
目录 1. Vue.js生命周期函数2.Vue生命周期函数代码beforeCreatecreatedbeforeMountmountedbeforeUpdateupdatedbeforeDestroydestroyed$nextTick$forceUpdate$destroy 3. UniApp独有的生命周期函数onLaunchonShowonHideonError 4.总结 在UniApp中,除了Vue.js的生命周…...
【动态规划】【记忆化搜索】【状态压缩】1681. 最小不兼容性
作者推荐 【数位dp】【动态规划】【状态压缩】【推荐】1012. 至少有 1 位重复的数字 本文涉及知识点 动态规划汇总 状态压缩 记忆化搜索 1681. 最小不兼容性 给你一个整数数组 nums 和一个整数 k 。你需要将这个数组划分到 k 个相同大小的子集中,使得同一…...
JVM-类加载器 双亲委派机制
申明:文章内容是本人学习极客时间课程所写,文字和图片基本来源于课程资料,在某些地方会插入一点自己的理解,未用于商业用途,侵删。 什么是JVM JVM是Java Virtual Machine(Java虚拟机)的缩写&a…...
vue axios 请求后端无法传参问题
vue请求后端无法传参问题 问题描述处理过程总结 问题描述 在学习vue时,使用axios调用后端,发现无法把参数正确传到后端,现象如下: 使用vue发起请求,浏览器上已经有传参,但是后端没接收到对应的用户名密码&…...
打印最小公倍数
打印最小公倍数 题目描述: 输入2个整数m和n,计算m和n的最小公倍数,并打印出结果 测试1: 输入:18 24 输出:72 测试2: 输入:18 6 输出:18解法思路: 最小公倍数是指两个…...
[AIGC] Java 和 Kotlin 的区别
好的,我还是以“萌萌哒小码农”的身份继续回答您的问题。 Java 和 Kotlin 是两种不同的编程语言,它们有许多共同点,但也有一些重要的区别。以下是一些常见的 Java 和 Kotlin 的区别: 语法 Kotlin 的语法比 Java 简洁得多&#…...
蓝桥杯电子类单片机提升一——超声波测距
前言 单片机资源数据包_2023 一、超声波测距原理 二、超声波测距的应用 1.超声波的发射 2.单片机知识补充:定时器 3.超声波的接收与计时 4.距离的计算 1)定时器1为16位自动重载+1T11.0592MHz 2)定时器1为16位自动重载&am…...
前端架构: 脚手架开发流程中的难点梳理
脚手架的开发流程 1 )开发流程 创建 npm 项目创建脚手架入口文件,最上方添加: #!/usr/bin/env node 配置 package.json, 添加 bin 属性编写脚手架代码将脚手架发布到 npm 2 )使用流程 安装脚手架 npm install -g your-own-cli …...
django中配置使用websocket
Django 默认情况下并不支持 WebSocket,但你可以通过集成第三方库如 channels 来实现 WebSocket 功能。channels 是一个 Django 应用,它提供了对 WebSocket、HTTP2 和其他协议的支持。 下面是如何在 Django 项目中使用 WebSocket 的基本步骤:…...
Rust复合类型详解
在Rust中,复合类型是一种能够将多个值组合在一起的数据类型。本篇博客将介绍两种常见的复合类型:元组(Tuple)和数组(Array)。 Tuple(元组) 元组是Rust中的一种复合类型,…...
学习 JavaScript 闭包
1. 前言 闭包是 JavaScript 中一种非常重要的概念,它允许函数访问其外部作用域中的变量,即使在函数被返回或者在其原始定义的作用域之外执行时仍然可以访问这些变量。 在讲解闭包之前我们得弄清楚下面的概念: 作用域链: JavaSc…...
VScode中配置 C/C++ 环境 | IT拯救者
文章目录 0 引言1. 下载编辑器VScode2. 下载编译器MinGW并解压3. 将MinGW添加至环境变量4. 配置VScode插件5. 运行代码6. 调整和优化7. 提示8. 例行格式条款9. 例行格式条款 0 引言 由于VScode毛毛张使用不习惯,因此配置教程记不住,不过毛毛张看到一篇不…...
基于Python实现Midjourney集成到(个人/公司)平台中
目前Midjourney没有对外开放Api,想体验他们的服务只能在discord中进入他们的频道进行体验或者把他们的机器人拉入自己创建的服务器中;而且现在免费的也用不了了,想使用就得订阅。本教程使用midjourney-api这个开源项目,搭建Midjou…...
蓝桥杯刷题--python-6
0最大距离 - 蓝桥云课 (lanqiao.cn) n=int(input()) nums=list(map(int,input().split()))max_=float(-inf) for i in range (n):for j in range (i+1,n):tmp=abs(i-j)+abs(nums[i]-nums[j])max_=max(tmp,max_) print(max_) 0最长递增 - 蓝桥云课 (lanqiao.cn) import os im…...
node+vue3+mysql前后分离开发范式——实现对数据库表的增删改查
文章目录 ⭐前言⭐ 功能设计与实现💖 node后端操作数据库实现增删改查💖 vue3前端实现增删改查⭐ 效果⭐ 总结⭐ 结束⭐结束⭐前言 大家好,我是yma16,本文分享关于 node+vue3+mysql前后分离开发范式——实现对数据库表的增删改查。 技术选型 前端:vite+vue3+antd 后端:…...
【Android】使用Apktool反编译Apk文件
文章目录 1. 下载Apktool1.1 Apktool官网下载1.2 百度网盘下载 2. 安装Apktool3. 使用Apktool3.1 配置Java环境3.2 准备Apk文件3.3 反编译Apk文件3.3.1 解包Apk文件3.3.2 修改Apk文件3.3.3 打包Apk文件3.3.4 签名Apk文件 1. 下载Apktool 要使用Apktool,需要准备好 …...
(04)Hive的相关概念——order by 、sort by、distribute by 、cluster by
Hive中的排序通常涉及到order by 、sort by、distribute by 、cluster by 一、语法 selectcolumn1,column2, ... from table [where 条件] [group by column] [order by column] [cluster by column| [distribute by column] [sort by column] [limit [offset,] rows]; …...
TDengine IDMP 工业数据建模 —— 数据标准化
3.4 数据标准化 工业环境通常从多个数据源采集数据,这些数据往往命名不一致、物理单位各异、数据结构不同。如果没有标准化,跨资产分析、AI 生成洞察和数据汇聚将变得不可靠甚至无法实现。TDengine IDMP 提供了多种机制,对整个资产模型中的数…...
2026实测不踩坑!6款成品PPT网站客观测评
2026实测不踩坑!6款成品PPT网站客观测评作为常年深耕AI工具测评的博主,日常需应对各类PPT创作需求,也经常收到粉丝咨询相关工具选择。经过实测多款成品PPT网站后,整理出6款适配性较强的平台,涵盖不同需求场景ÿ…...
离谱了,简历写了这个项目薪资直接涨了 80%!!
报喜了!!!前阵子帮一个粉丝修改简历,只是在项目经历里加了一个“不起眼”的项目,优化了表述逻辑,没想到他面试3家公司,2家给了offer,薪资直接比上一份涨了80%!其实很多人…...
告别台式机没麦克风的尴尬:用SonoBus+VB-Cable把手机秒变无线麦(保姆级配置)
台式机零成本无线麦克风方案:SonoBus与VB-Cable实战指南 你是否遇到过这样的尴尬时刻——台式电脑突然需要语音沟通,却发现没有麦克风?无论是紧急会议、游戏开黑还是直播互动,这种硬件缺失带来的困扰可能让你措手不及。本文将介绍…...
KityMinder云存储与分享功能完整指南:打造高效团队协作体验
KityMinder云存储与分享功能完整指南:打造高效团队协作体验 【免费下载链接】kityminder 百度脑图 项目地址: https://gitcode.com/gh_mirrors/ki/kityminder KityMinder作为百度FEX团队开发的在线思维导图工具,其强大的云存储与分享功能让团队协…...
毕业设计实战:基于Java+MySQL的教务管理系统设计与实现指南
毕业设计实战:基于JavaMySQL的教务管理系统设计与实现指南 在开发“基于JavaMySQL的教务管理系统”毕业设计时,曾因课程报名表未通过学生ID与课程ID双外键关联踩过关键坑——初期仅设计报名编号、报名时间等基础字段,未与学生表、课程表建立关…...
GLM-OCR模型Node.js环境配置与API服务搭建全指南
GLM-OCR模型Node.js环境配置与API服务搭建全指南 你是不是也遇到过这样的场景?手头有一堆图片需要提取文字,比如扫描的文档、截图或者手机拍的照片。自己手动录入?效率太低。用现成的在线OCR工具?又担心数据安全和调用限制。特别…...
保姆级教程:在RK3588上交叉编译Qt 5.15.15(含完整配置流程)
保姆级教程:在RK3588上交叉编译Qt 5.15.15(含完整配置流程) 在嵌入式开发领域,RK3588作为一款高性能的ARM处理器,正逐渐成为智能终端设备的首选平台。而Qt框架凭借其跨平台特性和丰富的GUI组件,为嵌入式界面…...
告别模糊边界!用Monodepth2实战KITTI深度估计,详解自动掩码与最小重投影损失
告别模糊边界!用Monodepth2实战KITTI深度估计,详解自动掩码与最小重投影损失 深度估计是计算机视觉领域的一项基础任务,它试图从2D图像中恢复出3D场景的几何信息。在自动驾驶、机器人导航、增强现实等应用中,准确的深度感知至关重…...
MelonLoader终极指南:Unity游戏Mod加载器从入门到精通
MelonLoader终极指南:Unity游戏Mod加载器从入门到精通 【免费下载链接】MelonLoader The Worlds First Universal Mod Loader for Unity Games compatible with both Il2Cpp and Mono 项目地址: https://gitcode.com/gh_mirrors/me/MelonLoader 还在为Unity游…...
