C++如何使用原子操作(std::atomic)?(无锁编程)

std::atomic是C++无锁编程核心工具,提供原子读写、CAS等操作,支持整型/指针等类型,需用load/store/exchange/compare_exchange等成员函数操作,不支持普通赋值和算术运算符。

在C++中,std::atomic是实现无锁编程的核心工具,它保证对共享变量的读、写、修改操作具有原子性,避免数据竞争,无需互斥锁(如std::mutex)即可安全地在多线程间同步。

基本用法:声明与初始化

使用std::atomic需包含头文件。它是一个模板类,支持整型、指针等可平凡复制(trivially copyable)类型。常用特化包括std::atomicstd::atomicstd::atomic等。

初始化方式有多种:

  • 直接构造:std::atomic counter{0};
  • 使用.store()赋值:counter.store(42);
  • 使用初始化列表(C++20起支持聚合初始化):std::atomic x = {100};

核心操作:load、store、exchange、compare_exchange

原子变量不支持普通赋值和算术运算符重载(如++),必须调用成员函数完成操作:

  • load():原子读取当前值,可指定内存序(如memory_order_acquire
  • store(val):原子写入,可选内存序(如memory_order_release
  • exchange(val):原子替换并返回旧值(类似“取-存”)
  • compare_exchange_weak/strong(expected, desired):CAS(Compare-And-Swap),最常用无锁原语。若当前值等于expected,则设为desired并返回true;否则将当前值写入expected并返回false。推荐用weak版本(可能伪失败,但性能更好),配合循环重试

示例(无锁计数器递增):

std::atomic counter{0};
int old_val = counter.load();
while (!counter.compare_exchange_weak(old_val, old_val + 1)) {
  // 若被其他线程抢先修改,old_val已更新,继续重试
}

内存序(memory order)控制可见性与重排

默认使用memory_order_seq_cst(顺序一致性),最安全也最慢。根据场景可降级以提升性能:

  • memory_order_relaxed:仅保证原子性,不约束前后指令重排,适用于计数器、引用计数等无需同步其他数据的场景
  • memory_order_acquire:用于读操作,保证该操作之后的读写不被重排到它前面(获取语义)
  • memory_order_release:用于写操作,保证该操作之前的读写不被重排到它后面(释放语义)
  • acquire-release配对常用于锁-free队列或信号量实现

错误示例:仅用relaxed实现“生产者-消费者”同步会出问题;正确做法是生产者store(..., release),消费者load(..., acquire)

常见陷阱与注意事项

  • std::atomic对象本身不可拷贝(禁用拷贝构造/赋值),但可移动(C++20起部分特化支持)
  • 不支持浮点类型(std::atomic在标准中未定义,GCC/Clang虽提供但非可移植);可用std::atomic加位转换模拟,但需谨慎处理NaN/符号位
  • 复合操作如fetch_addfetch_or等对整型和指针有完整支持,比手动CAS更简洁高效
  • 不要对std::atomic取地址传给非原子函数(如printf("%d", &x))——它不是普通变量
  • 调试困难:无锁代码一旦出错(如ABA问题、内存序误用),行为难以复现,建议先用锁验证逻辑,再逐步无锁化