c++ std::atomic如何保证原子性 c++ CAS操作原理是什么

来源:前端技术作者:台湾程序员头衔:程序员
导读:本期聚焦于小伙伴创作的《c++ std::atomic如何保证原子性 c++ CAS操作原理是什么》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《c++ std::atomic如何保证原子性 c++ CAS操作原理是什么》有用,将其分享出去将是对创作者最好的鼓励。

在多线程并发访问共享变量时,普通的读写操作可能会因为指令重排、缓存不一致等问题导致数据错误,而c++的std::atomic模板类就是用来保证变量操作的原子性,避免这类数据竞争问题的核心工具。它的底层实现和CAS操作紧密相关,理解这两部分内容能帮助开发者更好地运用无锁并发编程特性。

c++ std::atomic如何保证原子性 c++ CAS操作原理是什么

什么是操作的原子性

原子性指的是一个操作或者一系列操作要么全部执行完成,要么完全不执行,在执行过程中不会被其他线程打断。比如对一个普通int变量做自增操作,实际会拆分成读取旧值、计算新值、写回新值三个步骤,多线程下这三个步骤可能交叉执行,导致最终结果不符合预期。而原子操作会把这三个步骤合并成一个不可打断的整体,保证结果的正确性。

std::atomic保证原子性的实现方式

std::atomic的原子性保证并不是统一的,会根据不同的平台和变量类型采用不同的实现策略,主要分为以下几种情况:

1. 硬件原子指令支持

对于大多数基础类型,比如int、long、指针类型等,现代CPU都会提供原生的原子指令,比如x86架构的LOCK前缀指令。当std::atomic操作的类型大小不超过CPU的原子操作支持范围时,编译器会直接将这些操作编译成对应的硬件原子指令,从硬件层面保证操作的原子性。

比如下面的简单原子自增代码:

#include <atomic>
#include <iostream>

int main() {
    std::atomic<int> counter(0);
    // 原子自增操作,底层会编译为带LOCK前缀的指令
    counter++;
    std::cout << counter << std::endl;
    return 0;
}

2. 内部锁机制

如果std::atomic操作的类型大小超过了硬件支持的原子操作范围,比如自定义的大型结构体,编译器会在std::atomic内部嵌入一个轻量级的锁,通过对这个锁的获取和释放来保证操作的原子性。这种方式的性能会比硬件原子指令差一些,但依然能保证并发安全。

3. 内存顺序约束

除了操作本身的原子性,std::atomic还支持指定内存顺序参数,比如std::memory_order_relaxed、std::memory_order_acquire等,这些参数会约束编译器和CPU的指令重排行为,在保证原子性的同时,还能满足不同场景下的内存可见性需求。

CAS操作原理详解

CAS是Compare-And-Swap的缩写,也就是比较并交换,它是很多无锁并发算法和std::atomic底层实现的核心操作。

CAS的基本逻辑

CAS操作包含三个核心参数:内存地址V、旧的预期值A、要写入的新值B。操作的执行逻辑是:先判断内存地址V处的值是否等于预期值A,如果相等,就把新值B写入到地址V;如果不相等,就不做任何修改。整个操作是一个原子操作,不会被其他线程打断。操作完成之后会返回是否修改成功的布尔结果。

std::atomic中的CAS接口

std::atomic提供了compare_exchange_strong和compare_exchange_weak两个CAS相关的成员函数,两者的区别是weak版本的CAS可能会因为虚假失败返回false,即使当前值和预期值相等,而strong版本不会出现这种情况,但是strong版本的性能可能会稍差一些。

下面是一个使用CAS实现自旋锁的简单示例:

#include <atomic>
#include <thread>
#include <vector>
#include <iostream>

class SpinLock {
private:
    std::atomic<bool> flag{false}; // false表示锁未被占用
public:
    void lock() {
        // 循环尝试获取锁,直到成功
        bool expected = false;
        while (!flag.compare_exchange_weak(expected, true, std::memory_order_acquire)) {
            // 如果失败,重置expected为false,继续尝试
            expected = false;
        }
    }
    void unlock() {
        flag.store(false, std::memory_order_release);
    }
};

SpinLock spin_lock;
int shared_data = 0;

void thread_func(int n) {
    for (int i = 0; i < n; ++i) {
        spin_lock.lock();
        shared_data++;
        spin_lock.unlock();
    }
}

int main() {
    std::vector<std::thread> threads;
    for (int i = 0; i < 4; ++i) {
        threads.emplace_back(thread_func, 10000);
    }
    for (auto& t : threads) {
        t.join();
    }
    std::cout << "最终共享数据值: " << shared_data << std::endl;
    return 0;
}

CAS的底层实现

在x86架构下,CAS操作对应的底层指令是CMPXCHG,当这个指令加上LOCK前缀时,就能保证操作的原子性。LOCK前缀会让CPU在执行这条指令时锁住内存总线,防止其他CPU访问对应的内存地址,从而保证整个比较和交换的过程不会被打断。

不同架构的CPU会有不同的CAS指令实现,但是核心逻辑都是一致的:通过硬件提供的原子指令,保证比较和交换两个步骤的原子性。

CAS操作的注意事项

  • ABA问题:CAS操作只比较当前值和预期值是否相等,如果一个值从A变成B又变回A,CAS会认为值没有变化,从而导致逻辑错误。解决ABA问题的方式是给变量增加版本号,比如使用std::atomic<std::pair<int, int>>,其中第二个元素作为版本号,每次修改都递增版本号。
  • 循环开销:如果并发冲突比较严重,CAS操作可能会多次循环尝试,带来一定的CPU开销,这种情况下需要评估是否适合使用CAS实现相关逻辑。
  • weak和strong的选择:如果CAS操作在循环中使用,优先选择weak版本,因为即使出现虚假失败也只是多循环一次,性能更好;如果CAS操作不在循环中,优先选择strong版本,避免不必要的失败处理。

总结

std::atomic的原子性保证是分层实现的,小类型优先使用硬件原子指令,大类型使用内部锁机制,同时支持内存顺序约束满足可见性需求。CAS操作作为无锁编程的核心,通过硬件原子指令实现比较和交换的原子性,是std::atomic很多操作的基础实现。在实际开发中,需要根据场景选择合适的原子操作和内存顺序,同时注意CAS操作的ABA问题、循环开销等潜在问题,才能写出正确高效的并发代码。

std::atomicCAS操作原子性无锁编程修改时间:2026-06-30 23:39:33

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。