compare_exchange_weak 必须写在循环里,因为它可能伪失败(即使值匹配也返回 false),尤其在 arm 平台;expected 是输入-输出参数,需为可修改左值,循环中靠其更新实现自推进;weak 专为重试设计,strong 用于单次关键操作。

compare_exchange_weak 为什么必须写在循环里
因为它不保证一次成功——哪怕原子变量当前值真等于 expected,它也可能返回 false(伪失败),尤其在 ARM 平台。这不是 bug,是设计取舍:用可预期的重试换更轻量的指令(如 LDAXR/STLXR)。单次调用 compare_exchange_weak 几乎必然漏竞争或卡死。
常见错误现象:
-
int expected = 0; atomic_var.compare_exchange_weak(expected, 1);—— 值没被改过也返回false,且expected没更新,下轮还用 0,无限失败 -
if (atomic_var.compare_exchange_weak(expected, desired)) { ... }—— 竞争窗口从load到CAS一直开着,逻辑上就不成立
正确模式只有一种:int expected = atomic_var.load(); do { /* 构造 desired */ } while (!atomic_var.compare_exchange_weak(expected, desired));
expected 必须是非 const 的可修改左值引用
compare_exchange_weak 的 expected 参数不是只读输入,而是“输入-输出参数”:失败时,它会把原子变量当前真实值写回这个变量。这是循环能自推进的关键。
容易踩的坑:
- 声明为
const int exp = atomic_var.load();,再传给compare_exchange_weak—— 编译可能过,但函数无法覆写,exp永远不变 - 传字面量或临时量,比如
compare_exchange_weak(0, 1)—— 直接编译失败 - 用
std::atomic<int>::load()</int>结果直接传参,没存到变量里 —— 没地方让函数写回新值
记住:expected 是你和硬件之间的“通信寄存器”,得让它可写。
weak 和 strong 到底怎么选
不是“weak 不够强”,而是“weak 专为循环而生,strong 专为单次而设”。选错会导致性能反向或语义断裂。
适用场景:
- 用
compare_exchange_weak:所有带重试的无锁结构(栈push/pop、队列enqueue/dequeue、计数器递增);高并发热点路径(ARM 上吞吐比strong高 10%~15%) - 用
compare_exchange_strong:初始化标志位(只试一次)、失败需触发昂贵操作(如日志+告警)、业务不允许伪失败(如金融原子提交) - 混合策略可行:前 3–5 次用
weak,失败后切strong,防极端争用下伪失败堆积
性能影响:x86 上两者差异小;ARM 上 weak 生成单对 LDAXR/STLXR,strong 可能隐含额外分支和重试读,延迟更高。
指针 CAS 的 ABA 问题不是 compare_exchange_weak 的锅
裸指针 std::atomic<node></node> 的 compare_exchange_weak 正常工作,但无法防止 ABA:节点 A 被弹出、析构、新节点又分配到同一地址,CAS 误判“还是 A”,链表可能断裂。
这不是函数缺陷,是裸指针模型的固有局限。解决必须显式加防护:
- 用带版本号的 tagged pointer:
std::atomic<uint64_t></uint64_t>存(ptr >> 2) | (version & 0x3),CAS 时一起比 - 用
Hazard Pointer或 epoch-based reclamation 延迟节点回收,确保地址不被复用
别指望 compare_exchange_weak 自动帮你绕过 ABA——它只管“值是否相等”,不管“这个值是不是同一个东西”。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











