std::atomic不能直接替代锁,因其仅保证单个操作原子性,无法确保多操作组合(如读-改-写、条件判断+赋值)的整体原子性;需用CAS循环、mutex或事务设计来保障逻辑一致性。

std::atomic 不能直接实现无锁数据结构,只能保证单个变量的原子操作
很多人以为 std::atomic 一用,代码就“无锁”了——其实不是。它只保证对单个对象(如 int、bool、指针)的读、写、修改是原子的;但多个原子操作合起来,依然可能产生竞态。比如“先读再判再写”这种常见模式,std::atomic 默认不提供原子性保障,必须显式用 compare_exchange_weak 或 compare_exchange_strong 手动构造。
用 compare_exchange_weak 实现自旋锁或计数器递增
这是最常踩坑的地方:直接写 ++counter 看似简洁,但底层可能是 load-modify-store 三步,中间可能被抢占。正确做法是循环重试 + CAS:
std::atomic<int> counter{0};
int expected;
do {
expected = counter.load();
} while (!counter.compare_exchange_weak(expected, expected + 1));
</int>
-
compare_exchange_weak可能因硬件原因虚假失败(spurious failure),所以必须配合 do-while 循环 - 不要用
compare_exchange_strong替代——它在某些平台(如 ARM)开销更大,且不能避免所有重试场景 - 如果操作逻辑复杂(比如要同时更新两个字段),
std::atomic本身无法覆盖,得换用std::atomic_ref(C++20)或退回到互斥锁
指针型 std::atomic 的典型误用:ABA 问题
用 std::atomic 实现栈或队列时,容易忽略 ABA 问题:线程 A 读到指针 p,被调度走;线程 B 把 p 指向的节点弹出、释放、又新建一个新节点,恰好复用了同一内存地址,再压入;线程 A 回来执行 CAS,发现地址没变就成功了,但语义已错乱。
- 标准库不自动解决 ABA,需手动加版本号(如
std::atomic存“指针+tag”) - Boost.Lockfree 提供了带 tag 的
atomic_node,但 STL 没有等价物 - C++20 的
std::atomic_ref也不解决 ABA,只是扩展了可原子化类型的范围
memory_order 选错会导致看似正常却偶发崩溃
默认的 std::memory_order_seq_cst 最安全但性能差;很多教程教人盲目换成 relaxed,结果在弱一致性架构(ARM/PowerPC)上出错。真实取舍要看同步意图:
- 计数器累加:
fetch_add(1, std::memory_order_relaxed)足够(只要不依赖其他变量顺序) - 发布初始化后的资源(如单例指针):写端用
memory_order_release,读端用memory_order_acquire - 完全去掉顺序约束(
relaxed)后,编译器和 CPU 都可能重排指令,导致读到未初始化的字段
调试时加 -fsanitize=thread 能捕获部分重排问题,但无法替代对 memory_order 语义的理解。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











