一篇教你解决如何在不加锁的情况下解决多线程问题!
怎样在不加锁的情况下解决线程安全问题,你需要了解lock free和wait free这两个概念,在此之前我们先从最简单的有锁编程开始。
我们知道,多线程同时修改共享变量时会出现数据不一致的问题,比如多个线程同时对一个变量加1,假设count的初始值为0:
int count;void add() {++count;
}
如果只有一个线程调用add函数,那么什么问题都没有,但如果多个线程同时调用上述函数,比如10个线程都调用一遍,那么count值最后不一定等于0,原因在于对count加1的操作不是原子的 。
所谓某个操作是原子的是指CPU要么执行该操作,要么不执行该操作,不存在中间状态,但上述对count加1的操作经过编译器处理后会生成几条对应的机器指令,所以该操作不是原子的。
那么怎样才能让其变成原子的呢?很简单,加一把锁。
int count;
mutex mtx; // 锁void add() {mtx.lock();++count;mtx.unlock();
};
现在我们用一把锁将对count的操作保护了起来,此时你可以将mtx.lock()以及mtx.unlock()中间的代码看成原子的,CPU要完全执行完对count的加1要么根本不会操作count,这样上述程序的运行结果就是我们想要的了。
这是怎样做到呢?这就要说到操作系统了,千万不要小瞧了上面的mutex这把锁,这把锁的背后相当复杂,因为这涉及到了操作系统。
假设现在有三个线程,各自运行在不同的CPU核心上,每个方框代表一个时间片:

T1时间片这三个线程都在调用add函数,线程A拿到锁,A可以继续向前推进,但B和C就没这么幸运了,此时操作系统将剥夺线程B和C继续持有CPU的权利,将其分配给其它具备执行条件的线程,这就是操作系统中所谓的挂起,注意,这个过程相当复杂,因为这涉及到用户态与内核态的切换以及线程的切换等等。
此时来到T2时间片,线程A继续向前推进,线程B和C则被按下暂停键。
T3时间片,然而就在线程A拿到锁运行时因为某些原因像高优先级线程枪占之类导致操作系统也剥夺了线程A继续持有CPU的权利,糟糕的是,因为线程A此时持有锁,而线程A又无法继续向前推进,这就进一步使得线程B和C也无法继续向前推进。
你会发现在T3时刻,这几个线程都没有任何进展,根本原因在于我们为解决多线程问题加互斥锁惊动了操作系统,而这类互斥锁是操作系统给我们实现的,那么解决线程安全问题一定要经过操作系统吗?
不是的,在硬件层面也可以解决线程安全问题,硬件层面当然是指CPU,或者说机器指令。
CPU中有特定的原子指令,实际上操作系统也是基于这些指令实现的互斥锁,既然操作系统能用这些指令,我们(用户态)也可以使用这些指令,基于此我们可以将上述代码进行简单改造:
int count = 0;void add() {int old_value;do {old_value = count;} while (!atomic_compare_exchange(&count, &old_value, old_value + 1));
}
此时add函数是线程安全的,我们也没有对其进行加锁,不管多少线程同时调用add函数得到count都是正确的,而该函数的执行完全不涉及操作系统 ,不需要操作系统来维护秩序,利用的就是CPU中的原子指令,CPU在硬件层面一样可以替我们维护秩序。
资料直通车:Linux内核源码技术学习路线+视频教程内核源码
学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈
上述代码就是所谓lock-free的,不管操作系统怎样调度这三个线程,我们都能确保这三个线程中总有一个能继续向前推进 。
lock-free的系统看起来像这样:

对于这类系统不存在某个时间片下线程都无法推进的情况 ,换句话说就是lock-free程序保证至少有一个线程能继续向前推进。
可以看到,lock-free给出了比普通锁更优的保障。
但不能简单从代码是不是加锁或不加锁去判断代码是否lock-free ,回旋锁也是没有上述互斥锁的,也不经过操作系统,但回旋锁并不是lock-free的,如果你这样利用CPU中的原子操作修改add函数:
int count = 0;
int lock = 0; // 回旋锁void add () {int expected = 0;while(!atomic_compare_exchange_weak(&lock, &expected, 1))expected = 0;count++;lock = 0;
}
这就是典型的回旋锁,然而如果某个线程持有回旋锁后被操作系统挂起那么其它线程开始无效的执行死循环,除了白白消耗CPU之外它们都无法继续向前推进,显而易见,如果此时系统负载较高那么此类程序的性能会变差。
既然现在你已经知道了lock-free我们再继续优化这段代码:
std::atomic<int> count;void add() {++count;
}
这段代码没有锁,也不需要用循环不断检测是否有其它线程修改count,不管操作系统如何调度这三个线程,它们都能在有限的操作数内执行完成 ,此时我们说该程序是wati-free的,wait-free系统运行起来像这样:

可以看到在任意时间片内,不管操作系统怎样调度,所有线程都能向前推进 。
wait-free比lock-free的要求更高更加严格,由于wait-free的程序总是能在有限的步骤内执行完成,因此实时性是最好的,适用于那些对实时性要求较高的场景,当然实现难度也要比lock-free更高。
值得注意的是,wait-free以及lock-free程序的实现通常不是那么简单。

相关文章:
一篇教你解决如何在不加锁的情况下解决多线程问题!
怎样在不加锁的情况下解决线程安全问题,你需要了解lock free和wait free这两个概念,在此之前我们先从最简单的有锁编程开始。 我们知道,多线程同时修改共享变量时会出现数据不一致的问题,比如多个线程同时对一个变量加1ÿ…...
OPT(奥普特)一键测量传感器SmartFlash高精度的四重保证
OPT(奥普特)一键测量传感器SmartFlash集成了机器视觉的边缘提取、自动匹配、自动对焦、自动学习及图像合成等人工智能技术,采用双远心光路及多角度照明系统设计,搭载高精度运动平台,并通过亚像素边缘提取算法处理图像&…...
网络协议丨从物理层到MAC层
我们都知道TCP/IP协议其中一层,就是物理层。物理层其实很好理解,就是物理攻击的物理。我们使用电脑上网时的端口、网线这些都属于物理层,没有端口没有路由你没有办法上网。网线的头我们叫水晶头,也是物理层的一份子。如果你的面前…...
【Maven】(五)Maven模块的继承与聚合 多模块项目组织构建
文章目录1.前言2.模块的继承2.1.可继承的标签2.2.超级POM2.3.手动引入自定义父POM3.模块的聚合3.1.聚合的注意事项3.2.反应堆(reactor)4.依赖管理及属性配置4.1.依赖管理4.2.属性配置5.总结1.前言 本系列文章记录了 Maven 从0开始到实战的过程,Maven 系列历史文章清…...
Linux 常用软件安装(jdk,redis,mysql,minio,kkFileView)
1.jdk安装 查询所有跟Java相关的安装的rpm包 rpm -qa | grep java卸载所有跟openjdk相关的包: 执行命令。删除以上除了noarch 结尾的所有文件 rpm -e --nodeps java-1.8.0-openjdk-1.8.0.252.b09-2.el8_1.x86_64 rpm -e --nodeps java-1.8.0-openjdk-headless-1.8.0…...
单链表及其相关函数
实现功能BuySListNode ————————————申请一个新节点并赋值SListLength —————————————计算链表的长度SListPushBack————————————尾插SListPushFront————————————头插SListPopBack—————————————尾删SListPopFront—…...
Linux段错误调试
1、设置ulimit ulimit -a 查看 ulimit -c 2048 设置core大小 2、设置core文件信息 下面两个设置需要在root下设置,否则权限不通过 echo 1>/proc/sys/kernel/core_uses_pid echo "/tmp/corefile-%e-%p-%t" >/proc/sys/kernel/core_pattern 3、编译…...
Gopro卡无法打开视频恢复方法
下边来看一个文件系统严重受损的Gopro恢复案例故障存储: 120G SD卡故障现象:客户正常使用,备份数据时发现卡无法打开,多次插拔后故障依旧。故障分析:Winhex查看发现0号分区表扇区正常,这应该是一个exfat格式的文件系统,但是逻辑盘…...
vmware虚拟机与树莓派4B安装ubuntu1804 + ros遇到的问题
如题所示,本人在虚拟机上安装ubuntu1804,可以很容易安装,并且更换系统apt源和ros源,然后安装ros,非常顺利,但是在树莓派4B上安装raspiberry系统就遇到了好多问题。 树莓派我烧录的是这个镜像:ub…...
JS逆向hook通用脚本合集
1. cookie 通用hook Cookie Hook 用于定位 Cookie 中关键参数生成位置,以下代码演示了当 Cookie 中匹配到了 v 关键字, 则插入断点 (function () {var cookieTemp ;Object.defineproperty(document, cookie, {set: function (val) {if (val.indexOf(v…...
nacos的介绍和下载安装(详细)
目录 一、介绍 1.什么是nacos(含有官方文档)? 2.nacos的作用是什么? 3.什么是nacos注册中心? 4.核心功能 二、下载安装 一、介绍 1.什么是nacos(含有官方文档)? 一个更易于…...
【算法经典题集】前缀和与数学(持续更新~~~)
😽PREFACE🎁欢迎各位→点赞👍 收藏⭐ 评论📝📢系列专栏:算法经典题集🔊本专栏涉及到的知识点或者题目是算法专栏的补充与应用💪种一棵树最好是十年前其次是现在前缀和一维前缀和k倍…...
寻找时空中的引力波:科学家控制量子运动至量子基态
据英国每日邮报报道,时空织布里的涟漪或可以揭示宇宙在140亿年前是如何产生的,然而寻找这些名为“引力波”的涟漪却一直难以捉摸。现在美国科学家们声称他们发现了改善用于检测宇宙大爆炸的引力波的探测器的方法。 宇宙大爆炸残留的引力波 美国加州理…...
第六讲:ambari-web 模块二次开发
上述图片为 Ambari 部署及操作 hdp 集群相关的部分界面截图。这些页面如果想调整的话,比如汉化,二次开发等,则可以修改 ambari-web 模块的源码来实现。 一、介绍 ambari-web 模块涉及到的界面有: HDP 集群部署向导已安装服务的仪表板、配置界面等主机列表及详细信息告警列…...
echarts--提示框显示不全问题记录
最近接手一个同事之前做的网页,发现里面使用echarts来绘制各类图表;有2个问题一个是提示框显示不全,另一个就是绘制总是有部分数据显示不全。后者就是div宽度问题。。。无语,说下前面一个问题吧,记录一下。 tooltip组…...
LeetCode 1653. 使字符串平衡的最少删除次数
LeetCode 1653. 使字符串平衡的最少删除次数 难度:middle\color{orange}{middle}middle Rating:1794\color{orange}{1794}1794 题目描述 给你一个字符串 sss ,它仅包含字符 ′a′a′a′ 和 ′b′b′b′ 。 你可以删除 sss 中任意…...
聊一聊代码重构——程序方法和类上的代码实践
使用工厂方法取代构造方法 构造方法的问题 我们使用构造方法来初始化对象时候,我们得到的只能是当前对象。而使用工厂方法替换构造方法,我们可以返回其子类型或者代理类型。这让我们可以通过不同的实现类来进行逻辑实现的变化。 更重要的一点是&#…...
嵌入式学习笔记——寄存器开发STM32 GPIO口
寄存器开发STM32GPIO口前言认识GPIOGPIO是什么GPIO有什么用GPIO怎么用STM32上GPIO的命名以及数量GPIO口的框图(重点)输入框图解析三种输入模式GPIO输入时内部器件及其作用1.保护二极管2.上下拉电阻(可配置)3.施密特触发器4.输入数…...
[ 攻防演练演示篇 ] 利用通达OA 文件上传漏洞上传webshell获取主机权限
🍬 博主介绍 👨🎓 博主介绍:大家好,我是 _PowerShell ,很高兴认识大家~ ✨主攻领域:【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 🎉点赞➕评论➕收藏 养成习…...
程序设计与 C 语言期末复习
程序设计与 C 语言 1.计算机语言与编译 机器语言:一串仅由 0 和 1 序列表示的语言。计算机只能识别和接受 0 和 1 组成的指令。 符号语言(汇编语言):用一些英文字母和数字表示一个指令。 符号语言(汇编语言…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...
Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
调用支付宝接口响应40004 SYSTEM_ERROR问题排查
在对接支付宝API的时候,遇到了一些问题,记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...
为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...
