C++ 原子操作完全指南 + 单例模式实战
C 原子操作完全指南 单例模式实战一、std::atomic 基础1.1 基本用法std::atomic保证对变量的读写是不可中断的线程安全无需显式加锁。#includeatomic#includethread#includeiostreamstd::atomicintcounter{0};voidincrement(){for(inti0;i100000;i){counter;// 原子自增线程安全}}intmain(){std::threadt1(increment);std::threadt2(increment);t1.join();t2.join();std::coutcounter\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::atomicinta{10};intxa.load();// x 10a.store(20);// a 20intolda.exchange(30);// old 20, a 30intexpected30;booloka.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读-修改-写操作如 CASmemory_order_seq_cst全局顺序一致性默认简单但最慢2.2 Acquire-Release 同步示例#includeatomic#includethreadstd::atomicintdata{0};std::atomicboolready{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::coutdata.load(std::memory_order_relaxed)\n;// 一定输出 42}intmain(){std::threadt1(producer);std::threadt2(consumer);t1.join();t2.join();}关键release写和acquire读配对形成一个同步点。保证在release之前的所有写操作对acquire之后的读操作可见。三、自旋锁实现CAS 实战#includeatomic#includethreadclassSpinLock{std::atomic_flag flagATOMIC_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;intshared0;voidwork(){for(inti0;i10000;i){spinlock.lock();shared;// 临界区spinlock.unlock();}}C20 起std::atomic_flag的test()方法可以只读不修改。四、无锁队列简单示例templatetypenameTclassLockFreeStack{structNode{T data;Node*next;};std::atomicNode*head{nullptr};public:voidpush(constTvalue){Node*new_nodenewNode{value,nullptr};// CAS 循环如果 head 没变就更新为 new_nodedo{new_node-nexthead.load(std::memory_order_relaxed);}while(!head.compare_exchange_weak(new_node-next,// expectednew_node,// desiredstd::memory_order_release,std::memory_order_relaxed));}boolpop(Tresult){Node*old_headhead.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)){resultold_head-data;deleteold_head;// 实际工程中需要用 Hazard Pointer 或 RCUreturntrue;}}returnfalse;}};五、注意事项坑说明不是万能锁原子操作适合简单计数/标志复杂逻辑还是要用mutexABA 问题CAS 中值从 A→B→ACAS 认为没变。解决带标签的指针Tagged Pointer伪共享 (False Sharing)两个原子变量在同一缓存行不同 CPU 核频繁修改会互相拖累。解决alignas(64)只能用于平凡可复制类型std::atomicstd::string不行std::atomicint可以compare_exchange_weak vs strongweak可能伪失败被中断适合循环strong保证真失败才返回 false适合单次检查性能对比std::atomicinta{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_cstcorrectness first六、单例模式 原子操作实战6.1 基于 atomic 的双检查锁DCLPC11 之前 DCLP 有指令重排序问题C11 引入std::atomic后可以安全实现。#includeatomic#includemutex#includeiostreamclassSingleton{public:staticSingleton*getInstance(){Singleton*tmpinstance.load(std::memory_order_acquire);if(tmpnullptr){// 第一次检查无锁std::lock_guardstd::mutexlock(mutex);// 加锁tmpinstance.load(std::memory_order_relaxed);if(tmpnullptr){// 第二次检查有锁tmpnewSingleton();// release: 保证 new Singleton() 的构造完成// 对其它线程的 acquire 可见instance.store(tmp,std::memory_order_release);}}returntmp;}voiddoSomething(){std::coutSingleton::doSomething()\n;}private:Singleton()default;~Singleton()default;Singleton(constSingleton)delete;Singletonoperator(constSingleton)delete;staticstd::atomicSingleton*instance;staticstd::mutex mutex;};std::atomicSingleton*Singleton::instance{nullptr};std::mutex Singleton::mutex;// 使用intmain(){Singleton*sSingleton::getInstance();s-doSomething();}关键点instance.load(std::memory_order_acquire)确保看到其他线程release的完整构造对象instance.store(tmp, std::memory_order_release)确保构造完成后再发布指针双重检查第一次无锁快速路径第二次有锁保证唯一性6.2 基于 atomic_flag 的无锁单例更轻量如果单例构造很快可以用atomic_flag实现一个轻量级自旋锁版本的单例。#includeatomic#includethread#includeiostream#includevectorclassSpinLockSingleton{public:staticSpinLockSingleton*getInstance(){// 快速路径已初始化直接返回SpinLockSingleton*tmpinstance.load(std::memory_order_acquire);if(tmp!nullptr){returntmp;}// 慢速路径自旋等待初始化完成while(flag.test_and_set(std::memory_order_acquire)){// 自旋中检查是否其他线程已经初始化完成tmpinstance.load(std::memory_order_relaxed);if(tmp!nullptr){flag.clear(std::memory_order_release);// 释放锁虽然没拿到returntmp;}}// 拿到锁执行初始化tmpinstance.load(std::memory_order_relaxed);if(tmpnullptr){tmpnewSpinLockSingleton();instance.store(tmp,std::memory_order_release);std::coutSingleton initialized by thread\n;}flag.clear(std::memory_order_release);returntmp;}intgetValue()const{returnvalue;}private:SpinLockSingleton():value(42){}~SpinLockSingleton()default;staticstd::atomicSpinLockSingleton*instance;staticstd::atomic_flag flag;intvalue;};std::atomicSpinLockSingleton*SpinLockSingleton::instance{nullptr};std::atomic_flag SpinLockSingleton::flagATOMIC_FLAG_INIT;// 多线程测试intmain(){std::vectorstd::threadthreads;for(inti0;i10;i){threads.emplace_back([](){auto*sSpinLockSingleton::getInstance();std::coutValue: s-getValue()\n;});}for(autot:threads)t.join();}6.3 基于 call_once 的现代写法推荐生产环境虽然这不是纯 atomic 实现但它是最安全的现代 C 单例写法底层由标准库保证原子性。#includemutex#includeatomic#includeiostreamclassModernSingleton{public:staticModernSingletongetInstance(){std::call_once(initFlag,ModernSingleton::init);return*instance;}voiddoSomething(){std::coutModernSingleton::doSomething(), valuevalue\n;}private:ModernSingleton():value(100){}~ModernSingleton()default;staticvoidinit(){instancenewModernSingleton();}staticstd::once_flag initFlag;staticModernSingleton*instance;intvalue;};std::once_flag ModernSingleton::initFlag;ModernSingleton*ModernSingleton::instancenullptr;6.4 基于 static local variable 的最简写法C11 起线程安全classSimpleSingleton{public:staticSimpleSingletongetInstance(){staticSimpleSingleton instance;// C11 起保证线程安全初始化returninstance;}voiddoSomething(){}private:SimpleSingleton()default;~SimpleSingleton()default;SimpleSingleton(constSimpleSingleton)delete;SimpleSingletonoperator(constSimpleSingleton)delete;};C11 标准规定函数内的static局部变量初始化是线程安全的由编译器插入原子操作保证。这是最推荐的写法。七、单例模式选择指南实现方式线程安全性能复杂度推荐场景static local✅ C11 标准保证最优无锁最低首选绝大多数场景std::call_once✅优低需要延迟初始化且构造可能抛异常atomic DCLP✅优中学习原子操作原理、需要手动控制内存序atomic_flag 自旋锁✅良中构造极快、不想用 mutexmutex✅一般低简单场景不追求极致性能八、一句话总结std::atomic提供无锁的原子操作通过内存序控制可见性和重排序。relaxed最快但最弱release/acquire是性能与正确性的平衡点seq_cst最安全但最慢。CAS 是实现无锁数据结构的基石要小心 ABA 和伪共享。单例模式优先用 C11 的static local变量学习原子操作原理时可以用atomic DCLP实现。声明本文档由 AIMoonshot AI / Kimi辅助生成内容仅供参考请根据实际情况核实和使用。 仅供学习使用