compare_exchange_weak 总是返回 false,根本原因是它允许伪失败且必须在循环中使用:若 expected 非可修改的非 const 引用,失败时无法更新为最新值,导致重试始终用旧值而无限失败;同时,从读取到 CAS 的间隙存在真实竞争,单次调用无法保证原子性。

必须写在循环里,且 expected 必须是可修改的非 const 引用——漏掉任一条件,大概率死循环或逻辑错。
为什么 compare_exchange_weak 总是返回 false?
常见现象:刚初始化一个 std::atomic,设 expected = 0,调一次 compare_exchange_weak(expected, 1) 就返回 false,但值根本没被改过。
- 根本原因有两个:
compare_exchange_weak允许伪失败(spurious failure),尤其在 ARM 等弱内存模型平台;更关键的是——它会覆写expected为原子变量当前值 - 如果传入的是字面量、临时变量或
const int&,编译器可能报错;若侥幸通过(比如绑定到const变量),expected无法被更新,下次重试仍用旧值,导致无限失败 -
expected不是“输入参数”,而是“输入-输出参数”:成功时用于比对,失败时被重写为最新值,是你下一轮循环的起点
compare_exchange_weak 必须配 do-while 或 while 循环
CAS 循环不是为了应付伪失败,而是为了应对真实竞争:从读取 expected 到执行 CAS 这段间隙,其他线程可能已修改原子变量。
- ❌ 错误写法:
int expected = atomic_var.load(); if (atomic_var.compare_exchange_weak(expected, desired)) { ... }—— 单次判断毫无意义,必然漏竞争 - ✅ 正确模式:
int expected = atomic_var.load(); do { /* 基于 expected 构造 desired */ } while (!atomic_var.compare_exchange_weak(expected, desired)); - ⚠️ 注意:循环体内不要做耗时操作(如
new、锁、IO),否则会放大争用影响;也不要无条件重算desired(比如每次加 1 但expected已变),可能陷入逻辑错误重试
weak 和 strong 到底怎么选?
区别不在“强弱”,而在是否容忍伪失败,以及底层指令开销。这不是性能高低题,是场景适配题。
-
compare_exchange_weak在 x86 上通常和strong性能一致;但在 ARM 上生成LDAXR/STLXR,比LDXR/STXR更轻量,代价就是允许伪失败 - 优先用
weak:所有循环重试场景(无锁栈 push/pop、计数器递增、状态机跃迁) - 考虑用
strong:单次尝试逻辑(如初始化标志位)、失败需触发昂贵回退(如日志+告警)、或伪失败会破坏业务语义(如金融事务原子提交) - 混合策略可行:前 N 次用
weak,失败后切strong,避免极端争用下伪失败堆积
指针类型 CAS 容易踩的 ABA 问题怎么防?
用 std::atomic 实现无锁链表时,典型错误是节点 A 被弹出、析构、新节点又恰好分配到同一地址,CAS 误判“还是原来的 A”,导致数据损坏。
- 根本原因:
compare_exchange_weak只比较指针值,不关心指针指向的对象是否为同一逻辑实体 - 裸指针 CAS 无法自动防 ABA;
std::shared_ptr也不是银弹——它的原子特化只保证指针本身原子,不解决引用计数竞态 - 实用方案:引入版本号(
std::atomic,高 32 位存版本,低 32 位存指针),或使用std::atomic<:pair int>>手动打包 - 注意:任何 ABA 防护都增加内存和计算开销,得权衡场景——高频小对象队列建议用带版本的环形缓冲区,而非链表
真正难的不是写对语法,而是在循环里保持 expected 的语义一致性:它既是快照,又是失败后的纠偏依据;一旦在重试中混入外部状态或副作用,整个 CAS 逻辑就不可预测了。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











