compare_exchange_weak必须置于do-while或while循环中,且expected必须为可修改的非const引用;否则因伪失败和expected未更新导致逻辑错误或死循环。

compare_exchange_weak 必须写在循环里,否则大概率失败
它不是“试一次看行不行”的函数,而是“直到成功为止”的操作。伪失败(spurious failure)在 ARM 等弱内存模型平台是合法行为,compare_exchange_weak 可能在值确实匹配时也返回 false。如果只调用一次,且没做重试,逻辑就断了。
更危险的是:失败时,expected 会被覆写为原子变量当前值——这是它帮你省去额外 load() 调用的关键机制。但若 expected 是 const、字面量或临时量,编译器可能报错;侥幸通过则 expected 无法更新,下次重试仍拿旧值比,陷入死循环。
-
int expected = atomic_var.load(); while (!atomic_var.compare_exchange_weak(expected, desired)) { }✅ 正确:expected可修改,每次失败后自动刷新 -
atomic_var.compare_exchange_weak(0, 1);❌ 错误:传入字面量,无法绑定非 const 引用 -
const int exp = atomic_var.load(); atomic_var.compare_exchange_weak(exp, 1);❌ 危险:引用绑定到 const,函数无法更新它,循环卡死
compare_exchange_weak 和 compare_exchange_strong 怎么选
不是“strong 更可靠所以无脑用”,而是看场景对失败容忍度和性能敏感度的权衡。
compare_exchange_weak 在 x86 上通常和 strong 性能一致,但在 ARM 上生成更轻量指令(如 LDAXR/STLXR),代价就是允许伪失败;compare_exchange_strong 保证仅当值真实不等才失败,但重试成本更高——它内部可能用循环模拟 weak 的语义。
立即学习“C++免费学习笔记(深入)”;
- 绝大多数无锁结构(栈、队列头插/尾插)首选
compare_exchange_weak+ 循环,这是标准写法 - 极少数对单次失败开销极度敏感、且能确保低争用的场景,可考虑
compare_exchange_strong - 如果 weak 循环中连续多次伪失败(比如 >10 次),可 fallback 到
compare_exchange_strong避免极端抖动
指针类型 CAS 容易触发 ABA 问题,这不是 compare_exchange_weak 的 bug
当用 std::atomic<node></node> 实现无锁链表时,典型 ABA 场景是:节点 A 被弹出 → 内存释放 → 新节点恰好分配到同一地址 → CAS 误判“还是原来的 A”。这跟 compare_exchange_weak 是否伪失败无关,是裸指针 CAS 的固有缺陷。
解决必须显式引入额外状态,不能靠换用 strong 或改循环逻辑绕过:
- 加版本号(tagged pointer):用
std::atomic<uint64_t></uint64_t>存指针+计数,CAS 时一起比较高位版本和低位地址 - 延迟回收:用 Hazard Pointer 或 epoch-based reclamation,确保指针被其他线程看到期间,原对象不会被析构复用
- 不推荐“先读再判断”这种手工规避,既难验证又易漏边角情况
memory_order 参数不是可选装饰,乱设会破坏正确性
compare_exchange_weak 默认用 memory_order_seq_cst,安全但有性能代价。实际中常需按语义降级,但必须成对匹配:
- 成功路径(store)至少要
memory_order_release,否则其他线程可能看不到你写入的新值 - 失败路径(load)至少要
memory_order_acquire,否则后续读可能看到过期数据 - 若 success 设为
memory_order_relaxed,failure 也必须是memory_order_relaxed,否则编译失败 - 无锁队列的 tail 更新常用
memory_order_acq_rel,因为既要读当前 tail,又要写新 tail
真正容易被忽略的,是 failure 时的 memory_order ——很多人只关注“成功了怎么同步”,却忘了“失败了也要保证看到最新值”,而这恰恰是循环能收敛的前提。



















