C++11:原子操作与内存顺序:从理论到实践的无锁并发实现

news2025/6/8 19:43:33

文章目录

    • 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);  // 写x
    y.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;                                // 操作1
    ready.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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2404491.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

动力电池点焊机:驱动电池焊接高效与可靠的核心力量|比斯特自动化

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

【MySQL】10.事务管理

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

Bugku-CTF-Web安全最佳刷题路线

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

IT学习方法与资料分享

一、编程语言与核心技能&#xff1a;构建技术地基 1. 入门首选&#xff1a;Python 与 JavaScript Python&#xff1a;作为 AI 与数据科学的基石&#xff0c;可快速构建数据分析与自动化脚本开发能力。 JavaScript&#xff1a;Web 开发的核心语言&#xff0c;可系统掌握 React/V…

jenkins gerrit-trigger插件配置

插件gerrit-trigger下载好之后要在Manage Jenkins -->Gerrit Trigger-->New Server 中新增Gerrit Servers 配置好保存后点击“状态”查看是否正常

数论总结,(模版与题解)

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

EasyRTC嵌入式音视频通信SDK助力物联网/视频物联网音视频打造全场景应用

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

1-2 Linux-虚拟机(2025.6.7学习篇- win版本)

1、虚拟机 学习Linux系统&#xff0c;就需要有一个可用的Linux系统。 如何获得&#xff1f;将自己的电脑重装系统为Linux&#xff1f; NoNo。这不现实&#xff0c;因为Linux系统并不适合日常办公使用。 我们需要借助虚拟机来获得可用的Linux系统环境进行学习。 借助虚拟化技术&…

Deepseek基座:Deepseek-v2核心内容解析

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

2025主流智能体Agent终极指南:Manus、OpenManus、MetaGPT、AutoGPT与CrewAI深度横评

当你的手机助手突然提醒"明天会议要带投影仪转接头"&#xff0c;或是电商客服自动生成售后方案时&#xff0c;背后都是**智能体(Agent)**在悄悄打工。这个AI界的"瑞士军刀"具备三大核心特征&#xff1a; 自主决策能力&#xff1a;像老司机一样根据路况实时…

家政小程序开发——AI+IoT技术融合,打造“智慧家政”新物种

基于用户历史订单&#xff08;如“每周一次保洁”&#xff09;、设备状态&#xff08;如智能门锁记录的清洁频率&#xff09;&#xff0c;自动生成服务计划。 结合天气数据&#xff08;如“雨天推荐玻璃清洁”&#xff09;&#xff0c;动态推送服务套餐。 IoT设备联动&#x…

Keil开发STM32生成hex文件/bin文件

生成hex文件生成bin文件 STM32工程的hex文件和bin文件都可以通过Keil直接配置生成 生成hex文件 工程中点击魔术棒&#xff0c;在 Output 中勾选 Create HEX File 选项&#xff0c;OK保存工程配置 编译工程通过后可以看到编译输出窗口有创建hex文件的提示 默认可以在Output文…

PDF 转 Markdown

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

北大开源音频编辑模型PlayDiffusion,可实现音频局部编辑,比传统 AR 模型的效率高出 50 倍!

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

蒲公英盒子连接问题debug

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

Unity | AmplifyShaderEditor插件基础(第五集:简易膨胀shader)

一、&#x1f44b;&#x1f3fb;前言 大家好&#xff0c;我是菌菌巧乐兹~本节内容主要讲一下&#xff0c;如何用shader来膨胀~ 效果预览&#xff1a; 二、&#x1f4a8;膨胀的基本原理 之前的移动是所有顶点朝着一个方向走&#xff0c;所以是移动 如果所有顶点照着自己的方…

WINUI——Magewell视频捕捉开发手记

背景 因需要融合视频&#xff0c;并加载患者CT中提取出的气管镜与病变&#xff0c;以便能实时查看气管镜是否在正确位置。 开发环境 硬件&#xff1a;Magewell的USB Capture HDMI Gen 2 IDE&#xff1a;VS2022 FrameWork: .Net6 WINUI Package: MVVMToolKit NLog Ma…

TDengine 开发指南——无模式写入

简介 在物联网应用中&#xff0c;为了实现自动化管理、业务分析和设备监控等多种功能&#xff0c;通常需要采集大量的数据项。然而&#xff0c;由于应用逻辑的版本升级和设备自身的硬件调整等原因&#xff0c;数据采集项可能会频繁发生变化。为了应对这种挑战&#xff0c;TDen…

第34次CCF-CSP认证真题解析(目标300分做法)

第34次CCF-CSP认证 矩阵重塑&#xff08;其一&#xff09;AC代码及解析矩阵重塑&#xff08;其二&#xff09;AC代码及解析货物调度AC代码及解析 矩阵重塑&#xff08;其一&#xff09; 输入输出及样例&#xff1a; AC代码及解析 1.线性化原矩阵 &#xff1a;由于cin的特性我们…

video-audio-extractor:视频转换为音频

软件介绍 前几天在网上看见有人分享了一个源码&#xff0c;大概就是py调用的ffmpeg来制作的。 这一次我带来源码版&#xff08;需要py环境才可以运行&#xff09;&#xff0c;开箱即用版本&#xff08;直接即可运行&#xff09; 软件特点 软件功能 视频提取音频&#xff1a…