C++11:原子操作与内存顺序:从理论到实践的无锁并发实现
文章目录
- 0.简介
- 1.并发编程需要保证的特性
- 2.原子操作
- 2.1 原子操作的特性
- 3.内存顺序
- 3.1 顺序一致性
- 3.2 释放-获取(Release-Acquire)
- 3.3 宽松顺序(Relaxed)
- 3.4 内存顺序
- 4.无锁并发
- 5. 使用建议
0.简介
在并发编程中,原子性、可见性和有序性是确保程序正确执行的三大特性。常见的保证这三个特性的操作是通过加锁来限制资源的访问,但这种方式会带来性能的降低,所以无锁编程变的日益常见,本文将对原子性、可见性和有序性进行介绍,同时介绍原子操作和内存顺序从而实现无锁的并发。
1.并发编程需要保证的特性
要理解原子性,可见性和有序性就需要先明确其对应问题,先从硬件架构和内存访问来看,现代的处理器为了提高性能,普遍采用的都是多级缓存技术和乱序执行技术。
1)多级缓存:每个cpu核心拥有独立的L1/L2缓存,共享L3缓存。这导致不同核心间可见性延迟,如对同一个变量的读写可能存在问题。
2)乱序执行:为了充分的利用指令流水线,只要不影响单线程内结果,指令是存在重排的可能性的,这就导致多线程下可能存在问题。
了解了面对的基础设施,接下来来看这三个特性如果不能保证会存在的问题:
1)原子性:其保证一次操作是原子的,比如var = var +1,假设不是原子的将其分为三步,取值,增加,写回,此时如果有两个线程分别都循环一百次增加var操作,这样线程间就可能互相取到尚未写回(也就是增加了但是没有回)的数值,此时在这基础上增加,就会导致未写回的增加丢失,从而导致结果错误,可以参考下图:
2)可见性:由于上面说的cpu多级缓存,可能存在没有同步到就读取的情况,比如通过bool值判断是否停止循环,这个bool在其他线程设置,此时就可能导致循环无法停止,cpu占用高。
3)有序性:是指执行的指令按照正常顺序执行不会因为重排带来问题,比如一个线程初始化数据,另一个线程使用,根据变量判断是否初始化完成,重排后可能存在问题,可以参考下面,假设这俩函数分别被两个线程执行,此时对于thread2来说,两个语句的先后不影响本身(也就是两个语句没有依赖关系),可能将bInit=true放到前面执行,a=new char[10]放到后面执行,此时thread1就可能出现使用到空指针情况。
char* a = nullptr;
int bInit = 0;
void thread1()
{if(bInit){//使用a}
}
void thread2()
{a = new char[10];bInit = true;
}
上面三个特性都可以通过加锁保证,但是锁内容不是本文主题,下面将描述原子操作和内存顺序如何保证这三个特性,从而实现无锁的编程。
2.原子操作
C++11引入了头文件,提供标准的原子类型和操作,下面是两个线程对于一个变量循环加一的操作例子:
#include <thread>
#include <iostream>
std::atomic<int> counter(0); // 原子整型
void worker() {for (int i = 0; i < 1000; ++i) {counter.fetch_add(1, std::memory_order_relaxed); // 原子加1}
}
int main() {std::thread t1(worker);std::thread t2(worker);t1.join();t2.join();std::cout << "Final counter value: " << counter << std::endl; // 输出2000
}
2.1 原子操作的特性
1)不可分割性:原子操作不会被其他线程中断。
2)内存可见性:操作结果按指定的内存顺序规则对其他线程可见。
2.2 原子操作的分类
1)读操作:采用load()
2)写操作:采用store()
3)修改操作:fetch_add()、fetch_sub()、compare_exchange_strong() 等
每种操作都可以指定内存顺序参数,控制操作可见性和有序性。
3.内存顺序
原子操作本身保证原子性后,可见性和有序性可以通过指定其内存顺序参数来保证,主要可以分为三类:
3.1 顺序一致性
顺序一致性比较好理解,所有的线程看到的原子操作顺序都相同,其执行顺序清晰且可以预测,但其性能开销较大,例子如下:
std::atomic<int> x(0), y(0);
void thread1() {x.store(1, std::memory_order_seq_cst); // 写xy.store(1, std::memory_order_seq_cst); // 写y
}
void thread2() {while (y.load(std::memory_order_seq_cst) == 0); // 等待y=1// 下面使用x一定是1
}
3.2 释放-获取(Release-Acquire)
释放-获取操作用于保证其调用前后的顺序。
1)释放操作(store+release):确保之前的写操作(如data=42)对其他线程可见。
2)获取操作(load+acquire):确保后续所有读操作(如使用data的地方)能看到释放操作前的所有写。
可以简单理解就是释放操作前的写操作不能重排到释放操作之后,获取操作后的读操作不能重排序到获取操作前。其可以用于像生产者消费者模型,锁机制等。
std::atomic<bool> ready(false);
int data = 0;
void producer() {data = 42; // 操作1ready.store(true, std::memory_order_release); // 释放操作(操作2)
}
void consumer() {while (!ready.load(std::memory_order_acquire)); // 获取操作(操作3)if(data == 42) xxx; //一定为true
}
3.3 宽松顺序(Relaxed)
其特点是只保证原子性,不去保证顺序和可见性,性能开销最小,适用于无关顺序的场景,如计数器等。
std::atomic<int> counter(0);
void increment() {counter.fetch_add(1, std::memory_order_relaxed);
}
3.4 内存顺序
除了上述基于原子操作的内存顺序,C++还显式的提供了内存屏障,其主要类型如下:
1)释放屏障:确保对于屏障前的所有写操作不会重排到屏障后。
2)获取屏障:防止屏障后的操作重排到屏障前。
3)全屏障:具有释放和获取的特性。
std::atomic<int> x(0), y(0);
void thread1() {x.store(1, std::memory_order_relaxed);std::atomic_thread_fence(std::memory_order_release); // 释放屏障y.store(1, std::memory_order_relaxed);
}
void thread2() {while (y.load(std::memory_order_relaxed) == 0) {}std::atomic_thread_fence(std::memory_order_acquire); // 获取屏障//x一定为1
}
4.无锁并发
有了上面的了解,可以来尝试实现一个无锁的队列,主要利用原子操作和内存顺序,去保证其原子性,可见性以及有序性,下面是一个简单的例子,可以参考。
#include <atomic>
#include <memory>
#include <iostream>
template<typename T>
class LockFreeQueue {
private:// 队列节点结构struct Node {T data; // 节点数据std::atomic<Node*> next; // 指向下一个节点的原子指针Node(const T& value) : data(value), next(nullptr) {}};std::atomic<Node*> head; // 队头指针std::atomic<Node*> tail; // 队尾指针
public:// 构造函数:初始化队头和队尾指针指向一个虚拟节点LockFreeQueue() {Node* dummy = new Node(T()); // 创建虚拟节点head.store(dummy, std::memory_order_relaxed);tail.store(dummy, std::memory_order_relaxed);}// 析构函数:释放队列中所有节点的内存~LockFreeQueue() {while (head.load() != nullptr) {Node* oldHead = head.load();head.store(oldHead->next.load(), std::memory_order_relaxed);delete oldHead;}}// 入队操作void enqueue(const T& value) {Node* newNode = new Node(value); // 创建新节点Node* oldTail = tail.load(std::memory_order_relaxed);// 使用 CAS 循环尝试将新节点添加到队尾while (true) {// 先尝试更新 tail 的 next 指针if (oldTail->next.compare_exchange_weak(nullptr, newNode,std::memory_order_release, // 释放语义:确保 newNode 的数据对其他线程可见std::memory_order_relaxed)) {// CAS 成功,更新 tail 指针指向新节点tail.compare_exchange_strong(oldTail, newNode,std::memory_order_relaxed,std::memory_order_relaxed);return;}// CAS 失败,说明其他线程已经更新了 tail->next// 更新 oldTail 为最新的 tail 值并重试oldTail = tail.load(std::memory_order_relaxed);}}// 出队操作bool dequeue(T& value) {Node* oldHead = head.load(std::memory_order_relaxed);// 使用 CAS 循环尝试出队while (true) {if (oldHead == tail.load(std::memory_order_relaxed)) {// 队列为空(只有虚拟节点)return false;}Node* nextNode = oldHead->next.load(std::memory_order_acquire);// 获取语义:确保能看到入队线程设置的 newNode 的数据// 尝试更新 head 指针if (head.compare_exchange_weak(oldHead, nextNode,std::memory_order_relaxed,std::memory_order_relaxed)) {// 获取数据(跳过虚拟节点)value = nextNode->data;delete oldHead; // 释放原头节点(虚拟节点或旧数据节点)return true;}// CAS 失败,说明其他线程已经更新了 head// 更新 oldHead 为最新的 head 值并重试oldHead = head.load(std::memory_order_relaxed);}}// 检查队列是否为空bool empty() const {return head.load(std::memory_order_relaxed) == tail.load(std::memory_order_relaxed);}
};
5. 使用建议
无锁的编程虽然在一定程度上提高了性能,但其也带来了复杂性和问题排查的困难,可以在对性能有要求且有足够测试的场景下使用。
原文链接:https://mp.weixin.qq.com/s/thnlXKZnKE4foxZ5Vi3NYQ
相关文章:

C++11:原子操作与内存顺序:从理论到实践的无锁并发实现
文章目录 0.简介1.并发编程需要保证的特性2.原子操作2.1 原子操作的特性 3.内存顺序3.1 顺序一致性3.2 释放-获取(Release-Acquire)3.3 宽松顺序(Relaxed)3.4 内存顺序 4.无锁并发5. 使用建议 0.简介 在并发编程中,原子性、可见性和有序性是…...
Android第十四次面试总结
OkHttp中获取数据与操作数据 一、数据获取核心机制 1. 同步请求(阻塞式) // 1. 创建HTTP客户端(全局应复用实例) OkHttpClient client new OkHttpClient();// 2. 构建请求对象(GET示例) Request r…...

动力电池点焊机:驱动电池焊接高效与可靠的核心力量|比斯特自动化
在新能源汽车与储能设备需求激增的背景下,动力电池的制造工艺直接影响产品性能与安全性。作为电芯与极耳连接的核心设备,点焊机如何平衡效率、精度与可靠性,成为电池企业关注的重点。 动力电池点焊机的核心功能是确保电芯与极耳的稳固连接。…...

【MySQL】10.事务管理
1. 事务的引入 首先我们需要知道CURD操作不加控制会产生什么问题: 为了解决上面的问题,CURD需要满足如下条件: 2. 事务的概念 事务就是一组DML语句组成,这些语句在逻辑上存在相关性,这一组DML语句要么全部成功&…...

Bugku-CTF-Web安全最佳刷题路线
曾经的我也是CTF六项全能,Web安全,密码学,杂项,Pwn,逆向,安卓样样都会。明明感觉这样很酷,却为何还是沦为社畜。Bugku-CTF-Web安全最佳刷题路线,我已经整理好了,干就完了…...

IT学习方法与资料分享
一、编程语言与核心技能:构建技术地基 1. 入门首选:Python 与 JavaScript Python:作为 AI 与数据科学的基石,可快速构建数据分析与自动化脚本开发能力。 JavaScript:Web 开发的核心语言,可系统掌握 React/V…...
程序代码篇---Python串口
在 Python 里,serial库(一般指pyserial)是串口通信的常用工具。下面为你介绍其常用的读取和发送操作函数及使用示例: 1. 初始化串口 要进行串口通信,首先得对串口对象进行初始化,代码如下: i…...

jenkins gerrit-trigger插件配置
插件gerrit-trigger下载好之后要在Manage Jenkins -->Gerrit Trigger-->New Server 中新增Gerrit Servers 配置好保存后点击“状态”查看是否正常...
虚拟主机都有哪些应用场景?
虚拟主机作为一种高效的网络托管方案,已经逐渐成为企业构建网站和应用软件的重要选择,下面,小编将为大家介绍一下虚拟主机的应用场景都有哪些吧! 虚拟主机可以帮助企业建立属于自己的企业网站,是用来展示公司形象和服务…...
预训练语言模型T5-11B的简要介绍
文章目录 模型基本信息架构特点性能表现应用场景 T5-11B 是谷歌提出的一种基于 Transformer 架构的预训练语言模型,属于 T5(Text-To-Text Transfer Transformer)模型系列,来自论文 Colin Raffel, Noam Shazeer, Adam Roberts, Kat…...

数论总结,(模版与题解)
数论 欧拉函数X质数(线性筛与二进制枚举)求解组合数欧拉降幂(乘积幂次)乘法逆元最小质因子之和模版 欧拉函数 欧拉函数的定义就是小于等于n的数里有f(n)个数与n互质,下面是求欧拉函数的模版。 package com.js.datas…...

EasyRTC嵌入式音视频通信SDK助力物联网/视频物联网音视频打造全场景应用
一、方案概述 随着物联网技术的飞速发展,视频物联网在各行业的应用日益广泛。实时音视频通信技术作为视频物联网的核心支撑,其性能直接影响着系统的交互体验和信息传递效率。EasyRTC作为一款成熟的音视频框架,具备低延迟、高画质、跨平台等…...

1-2 Linux-虚拟机(2025.6.7学习篇- win版本)
1、虚拟机 学习Linux系统,就需要有一个可用的Linux系统。 如何获得?将自己的电脑重装系统为Linux? NoNo。这不现实,因为Linux系统并不适合日常办公使用。 我们需要借助虚拟机来获得可用的Linux系统环境进行学习。 借助虚拟化技术&…...

Deepseek基座:Deepseek-v2核心内容解析
DeepSeek原创文章1 DeepSeek-v3:基于MLA的高效kv缓存压缩与位置编码优化技术 2 Deepseek基座:DeepSeek LLM核心内容解析 3 Deepseek基座:Deepseek MOE核心内容解析 4 Deepseek基座:Deepseek-v2核心内容解析 5Deepseek基座…...

2025主流智能体Agent终极指南:Manus、OpenManus、MetaGPT、AutoGPT与CrewAI深度横评
当你的手机助手突然提醒"明天会议要带投影仪转接头",或是电商客服自动生成售后方案时,背后都是**智能体(Agent)**在悄悄打工。这个AI界的"瑞士军刀"具备三大核心特征: 自主决策能力:像老司机一样根据路况实时…...

家政小程序开发——AI+IoT技术融合,打造“智慧家政”新物种
基于用户历史订单(如“每周一次保洁”)、设备状态(如智能门锁记录的清洁频率),自动生成服务计划。 结合天气数据(如“雨天推荐玻璃清洁”),动态推送服务套餐。 IoT设备联动&#x…...

Keil开发STM32生成hex文件/bin文件
生成hex文件生成bin文件 STM32工程的hex文件和bin文件都可以通过Keil直接配置生成 生成hex文件 工程中点击魔术棒,在 Output 中勾选 Create HEX File 选项,OK保存工程配置 编译工程通过后可以看到编译输出窗口有创建hex文件的提示 默认可以在Output文…...
Windows 系统安装 Redis 详细教程
Windows 系统安装 Redis 详细教程 一、Redis 简介 Redis(Remote Dictionary Server)是一个开源的、基于内存的高性能键值存储系统,常被用作数据库、缓存和消息中间件。相比传统数据库,Redis 具有以下优势: 超高性能…...
“组件、路由懒加载”,在 Vue3 和 React 中分别如何实现? (copy)
Vue3 和 React 组件懒加载实现方式 React 中组件懒加载的实现方式 React 提供了 React.lazy 和 Suspense 两个 API 来实现组件的懒加载。React.lazy 用于动态导入组件,而 Suspense 则用于指定加载过程中的占位内容。例如,可以通过以下代码实现懒加载&a…...
.NET 事件模式举例介绍
.NET 事件模式:实现对象间松耦合通信 在软件开发中,对象之间的通信是一个常见且重要的问题。.NET 框架提供了一种标准化的事件模式,用于解决对象间的通信问题,实现松耦合的交互方式。今天,我们就通过一个简单的例子来…...

PDF 转 Markdown
本地可部署的模型 Marker Marker 快速准确地将文档转换为 markdown、JSON 和 HTML。 转换所有语言的 PDF、图像、PPTX、DOCX、XLSX、HTML、EPUB 文件在给定 JSON 架构 (beta) 的情况下进行结构化提取设置表格、表单、方程式、内联数学、链接、引用和代…...

北大开源音频编辑模型PlayDiffusion,可实现音频局部编辑,比传统 AR 模型的效率高出 50 倍!
北大开源了一个音频编辑模型PlayDiffusion,可以实现类似图片修复(inpaint)的局部编辑功能 - 只需修改音频中的特定片段,而无需重新生成整段音频。此外,它还是一个高性能的 TTS 系统,比传统 AR 模型的效率高出 50 倍。 自回归 Tra…...

蒲公英盒子连接问题debug
1、 现象描述 2、问题解决 上图为整体架构图,其中左边一套硬件设备是放在机房,右边是放在办公室。左边的局域网连接了可以访问外网的路由器,利用蒲公英作为旁路路由将局域网暴露在外网环境下。 我需要通过蒲公英作为旁路路由来进行远程访问&…...

Unity | AmplifyShaderEditor插件基础(第五集:简易膨胀shader)
一、👋🏻前言 大家好,我是菌菌巧乐兹~本节内容主要讲一下,如何用shader来膨胀~ 效果预览: 二、💨膨胀的基本原理 之前的移动是所有顶点朝着一个方向走,所以是移动 如果所有顶点照着自己的方…...
Django核心知识点全景解析
引言 本文深入剖析Django核心组件,涵盖数据交换、异步交互、状态管理及安全认证,附完整代码示例和避坑指南! 目录 引言 一、JSON:轻量级数据交换标准 1. 核心特性 2. 标准格式 3. 各语言处理方法 4. 常见错误示例 二、AJA…...
生物发酵展同期举办2025中国合成生物学与生物制造创新发展论坛
一、会议介绍 2025中国合成生物学与生物制造创新发展论坛暨上海国际合成生物学与生物制造展览会于2025年8月7-9日在上海新国际博览中心(浦东新区龙阳路2345号)召开,本次论坛汇聚了国内外顶尖学者、行业领袖及政策制定者,将围绕“…...

WINUI——Magewell视频捕捉开发手记
背景 因需要融合视频,并加载患者CT中提取出的气管镜与病变,以便能实时查看气管镜是否在正确位置。 开发环境 硬件:Magewell的USB Capture HDMI Gen 2 IDE:VS2022 FrameWork: .Net6 WINUI Package: MVVMToolKit NLog Ma…...
Jetpack Compose 中,DisposableEffect、LaunchedEffect 和 sideEffect 区别和用途
在 Jetpack Compose 中,DisposableEffect、LaunchedEffect 和 sideEffect 都是用于处理副作用(Side Effects)的 API,但它们的用途和触发时机不同。以下是它们的核心概念和区别: 1. 副作用(Side Effect&…...
STM32开发,创建线程栈空间大小判断
1. 使用RTOS提供的API函数(以FreeRTOS为例) 函数原型:UBaseType_t uxTaskGetStackHighWaterMark(TaskHandle_t xTask)功能:获取指定任务堆栈中剩余的最小空间(以字为单位,非字节)。使用步骤&am…...
正则表达式检测文件类型是否为视频或图片
// 配置化文件类型检测(集中管理支持的类型) const FILE_TYPE_CONFIG {video: {extensions: [mp4, webm, ogg, quicktime], // 可扩展支持更多格式regex: /^video\/(mp4|webm|ogg|quicktime)$/i // 自动生成正则},image: {extensions: [jpeg, jpg, png,…...