C++ 原子操作完全指南 + 单例模式实战

📅 2026/7/29 21:49:55 👁️ 阅读次数 📝 编程学习
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_flagtest()方法可以只读不修改。


四、无锁队列(简单示例)

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 strongweak可能伪失败(被中断),适合循环;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)辅助生成,内容仅供参考,请根据实际情况核实和使用。 仅供学习使用