C++ 原子操作完全指南 + 单例模式实战
C++ 原子操作完全指南 + 单例模式实战
一、std::atomic 基础
1.1 基本用法
std::atomic保证对变量的读写是不可中断的(线程安全),无需显式加锁。
#include<atomic>#include<thread>#include<iostream>std::atomic<int>counter{0};voidincrement(){for(inti=0;i<100000;++i){counter++;// 原子自增,线程安全}}intmain(){std::threadt1(increment);std::threadt2(increment);t1.join();t2.join();std::cout<<counter<<"\n";// 保证输出 200000}1.2 核心 API
| 操作 | 说明 |
|---|---|
load() | 原子读 |
store(val) | 原子写 |
exchange(val) | 原子交换,返回旧值 |
compare_exchange_weak(expected, desired) | CAS,原子比较并交换 |
fetch_add(val)/fetch_sub(val) | 原子加减,返回旧值 |
++/-- | 重载运算符,等价于fetch_add(1) |
std::atomic<int>a{10};intx=a.load();// x = 10a.store(20);// a = 20intold=a.exchange(30);// old = 20, a = 30intexpected=30;boolok=a.compare_exchange_weak(expected,40);// 如果 a == 30,则 a = 40,返回 true// 如果 a != 30,则 expected = a 的当前值,返回 false二、内存序 (Memory Order)
std::atomic默认使用memory_order_seq_cst(顺序一致性),性能开销最大。可以显式指定更宽松的内存序。
2.1 五种内存序
宽松程度 seq_cst < acquire/release < acq_rel < relaxed (最强) (最弱,性能最好)| 内存序 | 含义 | 适用场景 |
|---|---|---|
memory_order_relaxed | 只保证原子性,不保证顺序 | 纯计数器(如引用计数) |
memory_order_acquire | 读操作:之后的读写不会被重排到这次读之前 | 获取锁、读取共享数据 |
memory_order_release | 写操作:之前的读写不会被重排到这次写之后 | 释放锁、发布共享数据 |
memory_order_acq_rel | 同时包含 acquire + release | 读-修改-写操作(如 CAS) |
memory_order_seq_cst | 全局顺序一致性(默认) | 简单但最慢 |
2.2 Acquire-Release 同步示例
#include<atomic>#include<thread>std::atomic<int>data{0};std::atomic<bool>ready{false};voidproducer(){data.store(42,std::memory_order_relaxed);// ① 写数据ready.store(true,std::memory_order_release);// ② 发布信号}voidconsumer(){while(!ready.load(std::memory_order_acquire))// ③ 等待信号;// ④ 保证能看到 ① 的写入std::cout<<data.load(std::memory_order_relaxed)<<"\n";// 一定输出 42}intmain(){std::threadt1(producer);std::threadt2(consumer);t1.join();t2.join();}关键:release写和acquire读配对,形成一个同步点。保证在release之前的所有写操作,对acquire之后的读操作可见。
三、自旋锁实现(CAS 实战)
#include<atomic>#include<thread>classSpinLock{std::atomic_flag flag=ATOMIC_FLAG_INIT;public:voidlock(){// test_and_set: 原子地设置 flag 为 true,返回旧值while(flag.test_and_set(std::memory_order_acquire)){// 自旋等待}}voidunlock(){flag.clear(std::memory_order_release);}};SpinLock spinlock;intshared=0;voidwork(){for(inti=0;i<10000;++i){spinlock.lock();++shared;// 临界区spinlock.unlock();}}C++20 起
std::atomic_flag的test()方法可以只读不修改。
四、无锁队列(简单示例)
template<typenameT>classLockFreeStack{structNode{T data;Node*next;};std::atomic<Node*>head{nullptr};public:voidpush(constT&value){Node*new_node=newNode{value,nullptr};// CAS 循环:如果 head 没变,就更新为 new_nodedo{new_node->next=head.load(std::memory_order_relaxed);}while(!head.compare_exchange_weak(new_node->next,// expectednew_node,// desiredstd::memory_order_release,std::memory_order_relaxed));}boolpop(T&result){Node*old_head=head.load(std::memory_order_acquire);while(old_head!=nullptr){if(head.compare_exchange_weak(old_head,old_head->next,std::memory_order_release,std::memory_order_relaxed)){result=old_head->data;deleteold_head;// 实际工程中需要用 Hazard Pointer 或 RCUreturntrue;}}returnfalse;}};五、注意事项
| 坑 | 说明 |
|---|---|
| 不是万能锁 | 原子操作适合简单计数/标志,复杂逻辑还是要用mutex |
| ABA 问题 | CAS 中值从 A→B→A,CAS 认为没变。解决:带标签的指针(Tagged Pointer) |
| 伪共享 (False Sharing) | 两个原子变量在同一缓存行,不同 CPU 核频繁修改会互相拖累。解决:alignas(64) |
| 只能用于平凡可复制类型 | std::atomic<std::string>不行,std::atomic<int>可以 |
| compare_exchange_weak vs strong | weak可能伪失败(被中断),适合循环;strong保证真失败才返回 false,适合单次检查 |
性能对比
std::atomic<int>a{0};// 最慢,但最直观a.store(1);// 等价于 memory_order_seq_cst// 快 2~10 倍(x86 上差距小,ARM 上差距大)a.store(1,std::memory_order_relaxed);经验法则:
- 如果不需要跨线程的happens-before关系,用
relaxed - 如果需要发布-订阅模式,用
release/acquire配对 - 如果搞不清,用默认的
seq_cst,correctness first
六、单例模式 + 原子操作实战
6.1 基于 atomic 的双检查锁(DCLP)
C++11 之前 DCLP 有指令重排序问题,C++11 引入std::atomic后可以安全实现。
#include<atomic>#include<mutex>#include<iostream>classSingleton{public:staticSingleton*getInstance(){Singleton*tmp=instance.load(std::memory_order_acquire);if(tmp==nullptr){// 第一次检查(无锁)std::lock_guard<std::mutex>lock(mutex);// 加锁tmp=instance.load(std::memory_order_relaxed);if(tmp==nullptr){// 第二次检查(有锁)tmp=newSingleton();// release: 保证 new Singleton() 的构造完成// 对其它线程的 acquire 可见instance.store(tmp,std::memory_order_release);}}returntmp;}voiddoSomething(){std::cout<<"Singleton::doSomething()\n";}private:Singleton()=default;~Singleton()=default;Singleton(constSingleton&)=delete;Singleton&operator=(constSingleton&)=delete;staticstd::atomic<Singleton*>instance;staticstd::mutex mutex;};std::atomic<Singleton*>Singleton::instance{nullptr};std::mutex Singleton::mutex;// 使用intmain(){Singleton*s=Singleton::getInstance();s->doSomething();}关键点:
instance.load(std::memory_order_acquire):确保看到其他线程release的完整构造对象instance.store(tmp, std::memory_order_release):确保构造完成后再发布指针- 双重检查:第一次无锁快速路径,第二次有锁保证唯一性
6.2 基于 atomic_flag 的无锁单例(更轻量)
如果单例构造很快,可以用atomic_flag实现一个轻量级自旋锁版本的单例。
#include<atomic>#include<thread>#include<iostream>#include<vector>classSpinLockSingleton{public:staticSpinLockSingleton*getInstance(){// 快速路径:已初始化直接返回SpinLockSingleton*tmp=instance.load(std::memory_order_acquire);if(tmp!=nullptr){returntmp;}// 慢速路径:自旋等待初始化完成while(flag.test_and_set(std::memory_order_acquire)){// 自旋中,检查是否其他线程已经初始化完成tmp=instance.load(std::memory_order_relaxed);if(tmp!=nullptr){flag.clear(std::memory_order_release);// 释放锁(虽然没拿到)returntmp;}}// 拿到锁,执行初始化tmp=instance.load(std::memory_order_relaxed);if(tmp==nullptr){tmp=newSpinLockSingleton();instance.store(tmp,std::memory_order_release);std::cout<<"Singleton initialized by thread\n";}flag.clear(std::memory_order_release);returntmp;}intgetValue()const{returnvalue;}private:SpinLockSingleton():value(42){}~SpinLockSingleton()=default;staticstd::atomic<SpinLockSingleton*>instance;staticstd::atomic_flag flag;intvalue;};std::atomic<SpinLockSingleton*>SpinLockSingleton::instance{nullptr};std::atomic_flag SpinLockSingleton::flag=ATOMIC_FLAG_INIT;// 多线程测试intmain(){std::vector<std::thread>threads;for(inti=0;i<10;++i){threads.emplace_back([](){auto*s=SpinLockSingleton::getInstance();std::cout<<"Value: "<<s->getValue()<<"\n";});}for(auto&t:threads)t.join();}6.3 基于 call_once 的现代写法(推荐生产环境)
虽然这不是纯 atomic 实现,但它是最安全的现代 C++ 单例写法,底层由标准库保证原子性。
#include<mutex>#include<atomic>#include<iostream>classModernSingleton{public:staticModernSingleton&getInstance(){std::call_once(initFlag,&ModernSingleton::init);return*instance;}voiddoSomething(){std::cout<<"ModernSingleton::doSomething(), value="<<value<<"\n";}private:ModernSingleton():value(100){}~ModernSingleton()=default;staticvoidinit(){instance=newModernSingleton();}staticstd::once_flag initFlag;staticModernSingleton*instance;intvalue;};std::once_flag ModernSingleton::initFlag;ModernSingleton*ModernSingleton::instance=nullptr;6.4 基于 static local variable 的最简写法(C++11 起线程安全)
classSimpleSingleton{public:staticSimpleSingleton&getInstance(){staticSimpleSingleton instance;// C++11 起保证线程安全初始化returninstance;}voiddoSomething(){}private:SimpleSingleton()=default;~SimpleSingleton()=default;SimpleSingleton(constSimpleSingleton&)=delete;SimpleSingleton&operator=(constSimpleSingleton&)=delete;};C++11 标准规定:函数内的
static局部变量初始化是线程安全的,由编译器插入原子操作保证。这是最推荐的写法。
七、单例模式选择指南
| 实现方式 | 线程安全 | 性能 | 复杂度 | 推荐场景 |
|---|---|---|---|---|
static local | ✅ C++11 标准保证 | 最优(无锁) | 最低 | 首选,绝大多数场景 |
std::call_once | ✅ | 优 | 低 | 需要延迟初始化且构造可能抛异常 |
atomic DCLP | ✅ | 优 | 中 | 学习原子操作原理、需要手动控制内存序 |
atomic_flag 自旋锁 | ✅ | 良 | 中 | 构造极快、不想用 mutex |
mutex | ✅ | 一般 | 低 | 简单场景,不追求极致性能 |
八、一句话总结
std::atomic提供无锁的原子操作,通过内存序控制可见性和重排序。relaxed最快但最弱,release/acquire是性能与正确性的平衡点,seq_cst最安全但最慢。CAS 是实现无锁数据结构的基石,要小心 ABA 和伪共享。单例模式优先用 C++11 的static local变量,学习原子操作原理时可以用atomic DCLP实现。
声明:本文档由 AI(Moonshot AI / Kimi)辅助生成,内容仅供参考,请根据实际情况核实和使用。 仅供学习使用
