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

从汇编角度解释线程间互斥-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的使用

多线程并发的竞态问题 我们创建三个线程同时进行购票&#xff0c;代码如下 #include<iostream> #include<thread> #include<list> using namespace std; //总票数 int ticketCount100; //售票线程 void sellTicket(int idx) {while(ticketCount>0){cou…...

高程 | 多态性(c++)

文章目录 &#x1f4da;多态&#x1f4da;运算符重载&#x1f407;定义&#x1f407;规则&#x1f407;友元运算符重载函数&#x1f407;成员运算符重载函数 &#x1f4da;虚函数&#x1f4da;纯虚函数和抽象类 &#x1f4da;多态 多态&#xff1a;同样的消息被不同类型的对象…...

LV.23 D2 开发环境搭建及平台介绍 学习笔记

一、Keil MDK-ARM简介及安装 Keil MDK&#xff0c;也称MDK-ARM&#xff0c;Realview MDK &#xff08;Microcontroller Development Kit&#xff09;等。目前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中&#xff0c;除了Vue.js的生命周…...

【动态规划】【记忆化搜索】【状态压缩】1681. 最小不兼容性

作者推荐 【数位dp】【动态规划】【状态压缩】【推荐】1012. 至少有 1 位重复的数字 本文涉及知识点 动态规划汇总 状态压缩 记忆化搜索 1681. 最小不兼容性 给你一个整数数组 nums​​​ 和一个整数 k 。你需要将这个数组划分到 k 个相同大小的子集中&#xff0c;使得同一…...

JVM-类加载器 双亲委派机制

申明&#xff1a;文章内容是本人学习极客时间课程所写&#xff0c;文字和图片基本来源于课程资料&#xff0c;在某些地方会插入一点自己的理解&#xff0c;未用于商业用途&#xff0c;侵删。 什么是JVM JVM是Java Virtual Machine&#xff08;Java虚拟机&#xff09;的缩写&a…...

vue axios 请求后端无法传参问题

vue请求后端无法传参问题 问题描述处理过程总结 问题描述 在学习vue时&#xff0c;使用axios调用后端&#xff0c;发现无法把参数正确传到后端&#xff0c;现象如下&#xff1a; 使用vue发起请求&#xff0c;浏览器上已经有传参&#xff0c;但是后端没接收到对应的用户名密码&…...

打印最小公倍数

打印最小公倍数 题目描述&#xff1a; 输入2个整数m和n&#xff0c;计算m和n的最小公倍数&#xff0c;并打印出结果 测试1&#xff1a; 输入&#xff1a;18 24 输出&#xff1a;72 测试2&#xff1a; 输入&#xff1a;18 6 输出&#xff1a;18解法思路: 最小公倍数是指两个…...

[AIGC] Java 和 Kotlin 的区别

好的&#xff0c;我还是以“萌萌哒小码农”的身份继续回答您的问题。 Java 和 Kotlin 是两种不同的编程语言&#xff0c;它们有许多共同点&#xff0c;但也有一些重要的区别。以下是一些常见的 Java 和 Kotlin 的区别&#xff1a; 语法 Kotlin 的语法比 Java 简洁得多&#…...

蓝桥杯电子类单片机提升一——超声波测距

前言 单片机资源数据包_2023 一、超声波测距原理 二、超声波测距的应用 1.超声波的发射 2.单片机知识补充&#xff1a;定时器 3.超声波的接收与计时 4.距离的计算 1&#xff09;定时器1为16位自动重载&#xff0b;1T11.0592MHz 2&#xff09;定时器1为16位自动重载&am…...

前端架构: 脚手架开发流程中的难点梳理

脚手架的开发流程 1 &#xff09;开发流程 创建 npm 项目创建脚手架入口文件&#xff0c;最上方添加&#xff1a; #!/usr/bin/env node 配置 package.json, 添加 bin 属性编写脚手架代码将脚手架发布到 npm 2 &#xff09;使用流程 安装脚手架 npm install -g your-own-cli …...

django中配置使用websocket

Django 默认情况下并不支持 WebSocket&#xff0c;但你可以通过集成第三方库如 channels 来实现 WebSocket 功能。channels 是一个 Django 应用&#xff0c;它提供了对 WebSocket、HTTP2 和其他协议的支持。 下面是如何在 Django 项目中使用 WebSocket 的基本步骤&#xff1a;…...

Rust复合类型详解

在Rust中&#xff0c;复合类型是一种能够将多个值组合在一起的数据类型。本篇博客将介绍两种常见的复合类型&#xff1a;元组&#xff08;Tuple&#xff09;和数组&#xff08;Array&#xff09;。 Tuple&#xff08;元组&#xff09; 元组是Rust中的一种复合类型&#xff0c…...

学习 JavaScript 闭包

1. 前言 闭包是 JavaScript 中一种非常重要的概念&#xff0c;它允许函数访问其外部作用域中的变量&#xff0c;即使在函数被返回或者在其原始定义的作用域之外执行时仍然可以访问这些变量。 在讲解闭包之前我们得弄清楚下面的概念&#xff1a; 作用域链&#xff1a; JavaSc…...

VScode中配置 C/C++ 环境 | IT拯救者

文章目录 0 引言1. 下载编辑器VScode2. 下载编译器MinGW并解压3. 将MinGW添加至环境变量4. 配置VScode插件5. 运行代码6. 调整和优化7. 提示8. 例行格式条款9. 例行格式条款 0 引言 由于VScode毛毛张使用不习惯&#xff0c;因此配置教程记不住&#xff0c;不过毛毛张看到一篇不…...

基于Python实现Midjourney集成到(个人/公司)平台中

目前Midjourney没有对外开放Api&#xff0c;想体验他们的服务只能在discord中进入他们的频道进行体验或者把他们的机器人拉入自己创建的服务器中&#xff1b;而且现在免费的也用不了了&#xff0c;想使用就得订阅。本教程使用midjourney-api这个开源项目&#xff0c;搭建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&#xff0c;需要准备好 …...

(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]; …...

Android Wi-Fi 连接失败日志分析

1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分&#xff1a; 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析&#xff1a; CTR…...

智慧医疗能源事业线深度画像分析(上)

引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

五年级数学知识边界总结思考-下册

目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解&#xff1a;由来、作用与意义**一、知识点核心内容****二、知识点的由来&#xff1a;从生活实践到数学抽象****三、知识的作用&#xff1a;解决实际问题的工具****四、学习的意义&#xff1a;培养核心素养…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

浅谈不同二分算法的查找情况

二分算法原理比较简单&#xff0c;但是实际的算法模板却有很多&#xff0c;这一切都源于二分查找问题中的复杂情况和二分算法的边界处理&#xff0c;以下是博主对一些二分算法查找的情况分析。 需要说明的是&#xff0c;以下二分算法都是基于有序序列为升序有序的情况&#xf…...

3-11单元格区域边界定位(End属性)学习笔记

返回一个Range 对象&#xff0c;只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意&#xff1a;它移动的位置必须是相连的有内容的单元格…...

以光量子为例,详解量子获取方式

光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学&#xff08;silicon photonics&#xff09;的光波导&#xff08;optical waveguide&#xff09;芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中&#xff0c;光既是波又是粒子。光子本…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

站群服务器的应用场景都有哪些?

站群服务器主要是为了多个网站的托管和管理所设计的&#xff0c;可以通过集中管理和高效资源的分配&#xff0c;来支持多个独立的网站同时运行&#xff0c;让每一个网站都可以分配到独立的IP地址&#xff0c;避免出现IP关联的风险&#xff0c;用户还可以通过控制面板进行管理功…...