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

compare_exchange_weak 必须写在 do-while 循环里
无锁队列的 tail 更新、head 弹出等操作,本质是“读当前值 → 算新值 → 原子写入”三步,中间任何时刻都可能被其他线程抢先修改。单次调用 compare_exchange_weak 几乎必然失败——不是因为值被改了,而是它在 ARM/AArch64 上允许伪失败(spurious failure),x86 虽不常见,但语义一致。不循环就等于放弃重试能力,逻辑直接中断。
正确结构只有一种:
int expected = tail.load(std::memory_order_acquire);
do {
Node* desired = expected->next;
} while (!tail.compare_exchange_weak(expected, desired, std::memory_order_release, std::memory_order_acquire));注意:expected 是普通变量,不是 const int,也不是字面量;每次失败后,expected 会被自动更新为 tail 当前真实值,下一轮直接可用。
expected 必须是非 const 的可修改左值引用
compare_exchange_weak 的第一个参数类型是 T&,它靠覆写这个引用所指内存来传递最新快照。这不是可选行为,而是循环能自维持的唯一机制。
立即学习“C++免费学习笔记(深入)”;
- ❌
const int exp = tail.load(); compare_exchange_weak(exp, ...):编译可能过,但函数无法写入,exp永远不变,循环卡死 - ❌
compare_exchange_weak(0, ...):字面量不能绑定非常量引用,编译失败 - ❌
int temp = tail.load(); compare_exchange_weak(temp, ...):若不在循环内重新加载或未被函数更新,就退化成固定值比对 - ✅
int expected = tail.load(); while(!tail.compare_exchange_weak(expected, ...)) { }:变量可写、生命周期覆盖全程、每次失败自动刷新
weak 和 strong 在队列尾指针更新中怎么选
无锁队列的 enqueue 中 tail 推进是典型高争用热点路径,compare_exchange_weak 是事实标准。它在 ARM 上生成 LDAXR/STLXR 指令,在 x86 上与 strong 性能几乎无差,但避免了 strong 内部隐式重试带来的分支预测开销。
常见反模式:
- 用
compare_exchange_strong替代 weak 试图“避免失败”:实测在高并发队列尾更新场景下吞吐低 10%~15% - 前几次用 weak、失败后切 strong:可行,但仅当连续伪失败 >8 次时才值得引入,否则增加分支复杂度
- 在循环体里做 new 分配或锁操作:CAS 循环必须轻量,否则自旋成本爆炸,反而放大争用
内存序建议:success 路径用 std::memory_order_release(确保节点构造完成可见),failure 路径用 std::memory_order_relaxed 即可——失败没写,无需同步语义。
指针类型 CAS 必须防 ABA,这不是 weak 的问题
用 std::atomic<node></node> 实现队列时,典型 ABA 场景是:tail 指向节点 A → A 被弹出并 delete → 新节点恰好分配到同一地址 → compare_exchange_weak 误判“还是原来的 A”,导致链表断裂。
这和 weak 是否伪失败无关,是裸指针 CAS 的固有缺陷。解决方式只能显式引入额外状态:
- 用带版本号的 tagged pointer:例如把
std::atomic<uint64_t></uint64_t>存(ptr >,CAS 时一起比 - 用 Hazard Pointer 或 epoch-based reclamation 延迟回收节点,确保地址不会被立即复用
- 不要指望换
strong或加 yield 能绕过 ABA —— 它们不解决根本问题
真正容易被忽略的是:哪怕你用了 weak + 循环 + 正确 expected,只要没处理 ABA,无锁队列在长时间运行后仍大概率崩溃或数据错乱。


















