compare_exchange_weak必须在循环中使用,因其允许伪失败且会更新expected值;expected须为非const左值引用;循环场景优先用weak,单次尝试用strong;指针CAS需防范ABA问题。

compare_exchange_weak 必须写在循环里,否则大概率失败
它不是“调一次试试看”的函数,而是专为循环重试设计的原子原语。不加循环直接调用,哪怕值完全匹配,也可能返回 false——这不是 bug,是标准允许的伪失败(spurious failure),尤其在 ARM 等弱内存模型平台常见。
关键在于:每次失败时,compare_exchange_weak 会把当前原子变量的真实值写回你传入的 expected 引用。如果没循环,这个更新就白费了;下次再调,还是拿旧值去比,结果永远 false。
- ✅ 正确模式:
int expected = atomic_var.load(); while (!atomic_var.compare_exchange_weak(expected, desired)) { } - ❌ 错误模式:
int expected = 0; atomic_var.compare_exchange_weak(expected, 1);(只调一次,且expected初始值固定) - ❌ 危险模式:
const int exp = atomic_var.load(); atomic_var.compare_exchange_weak(exp, 1);(exp是 const,函数无法更新它)
expected 必须是非 const 的左值引用,否则编译失败或逻辑崩溃
compare_exchange_weak 的第一个参数签名是 T& expected,强制要求可修改的左值。这是硬性约束,不是建议。
如果你传入字面量(如 0)、临时对象(如 atomic_var.load() 直接作为参数)、或绑定到 const 引用的变量,编译器可能报错(如 “cannot bind non-const lvalue reference to an rvalue”);侥幸通过的话,函数内部对 expected 的写入会被静默忽略,导致后续重试始终用过期值,陷入死循环。
立即学习“C++免费学习笔记(深入)”;
- 传
int expected = atomic_var.load();→ 安全,可被更新 - 传
auto expected = atomic_var.load();→ 危险,auto推导为int(值类型),不是引用 - 传
const int& exp = atomic_var.load();→ 编译失败或逻辑失效
compare_exchange_weak 和 compare_exchange_strong 怎么选?看场景,不看名字强弱
名字里的 “weak” 不代表“不可靠”,而是指它接受伪失败;“strong” 也不代表“更优”,只是保证仅当真实值不等时才失败。两者性能差异取决于平台:
- x86 上二者生成的指令常无区别,
compare_exchange_weak也几乎不伪失败 - ARM 上
compare_exchange_weak用LDAXR/STLXR,轻量但允许伪失败;compare_exchange_strong用LDXR/STXR,多一次校验,失败开销更高 - 循环场景(如无锁栈 push/pop)必须用
compare_exchange_weak:伪失败代价低,整体吞吐高 - 单次尝试场景(如初始化标志位、抢锁)适合
compare_exchange_strong:避免无谓重试
极端争用下,若 compare_exchange_weak 连续伪失败多次,可 fallback 到 compare_exchange_strong 避免饥饿,但这属于优化手段,非常规必需。
指针类型 CAS 的 ABA 问题不是 compare_exchange_weak 的锅,但你得自己防
用 std::atomic<node></node> 做无锁链表时,节点 A 被弹出、析构、新节点又分配到同一地址,CAS 会误判“还是原来的 A”,导致链表断裂或访问野指针。这跟 compare_exchange_weak 是否伪失败无关,是裸指针 CAS 的固有缺陷。
没有银弹,只有显式缓解方案:
- 用带版本号的 tagged pointer:
std::atomic<uint64_t></uint64_t>存低 48 位指针 + 高 16 位计数,CAS 时一起比 - 用 hazard pointer 或 epoch-based reclamation 延迟回收节点,确保旧地址不会被立即复用
- 避免在生命周期不确定的对象上直接做指针 CAS
最容易被忽略的一点:ABA 不会在调试器里明显暴露,往往在高并发压测或长时间运行后才触发崩溃或数据错乱,上线前必须专项验证。


















