compare_exchange_weak可能虚假失败,需配合循环重试;expected为引用且被自动更新;weak比strong轻量但需循环,x86-64性能相近;参数顺序和内存序须合规,典型应用如无锁栈push。

compare_exchange_weak 为什么总失败?
它不是“总失败”,而是设计上允许虚假失败(spurious failure)——即使当前值匹配预期,也可能返回 false。这是为了在某些架构(如 ARM、旧版 x86)上避免生成昂贵的强同步指令。你不能把它当普通 if 判断用,必须配合循环。
常见错误是写成单次调用:
std::atomic<int> a{0};
int expected = 0;
a.compare_exchange_weak(expected, 1); // ❌ 可能失败,但没重试
</int>
正确做法是循环重试,直到成功或值已变:
- 每次失败后,
expected会被自动更新为当前实际值 - 所以循环体里不用手动读取
a.load(),直接复用expected - 如果想区分“真失败”(值被别人改了)和“假失败”,需额外判断
expected是否仍等于你最初想要的值
compare_exchange_weak 和 compare_exchange_strong 的关键区别
两者语义一致:比较并交换,原子执行。区别只在失败行为:
-
compare_exchange_weak允许虚假失败,生成的汇编更轻量(尤其在非 x86-64 平台) -
compare_exchange_strong保证:只要值匹配,就一定成功(无虚假失败) - 在 x86-64 上,两者生成的指令通常相同(都用
cmpxchg),性能没差别 - 但在 ARM 或某些弱内存模型平台,
weak可能用ldxr/stxr循环实现,天然带虚假失败;strong则需额外屏障或重试逻辑
结论:默认用 compare_exchange_weak + 循环;只有当你明确不想处理循环(比如只试一次、且能容忍失败),才考虑 strong —— 但这种情况极少。
参数顺序和 memory order 容易错在哪
compare_exchange_weak 有两组重载,最常用的是:
bool compare_exchange_weak(T& expected, T desired,
std::memory_order success,
std::memory_order failure);
注意三点:
-
expected是引用,函数会修改它 —— 这是它和load()最大不同,别传临时变量或字面量 -
success和failure内存序不能随意乱设:failure必须 ≤success(例如不能success=memory_order_acq_rel而failure=memory_order_seq_cst) - 常见合理组合:
success=memory_order_acq_rel+failure=memory_order_acquire(读-修改-写场景);纯 flag 检查可用success=memory_order_relaxed+failure=memory_order_relaxed - 省略 memory order 参数时,默认用
memory_order_seq_cst,性能开销最大,别无必要时不用
实际用例:无锁栈 push 的典型写法
这是 compare_exchange_weak 最经典的应用场景,体现其“循环+更新 expected”的惯用法:
struct Node {
int data;
Node* next;
};
<p>std::atomic<node> head{nullptr};</node></p><p>void push(int data) {
Node<em> node = new Node{data, nullptr};
Node</em> current = head.load();
do {
node->next = current;
} while (!head.compare_exchange_weak(current, node));
}
</p>
这里的关键点:
-
current是局部变量,每次循环失败后自动被更新为最新head值 - 赋值
node->next = current必须在循环体内,否则可能链接到过期节点 - 没有锁,但依赖
compare_exchange_weak的原子性保证链表结构不被破坏 - 如果中途有其他线程修改了
head,current被重写,下次循环自然重试
虚假失败在这里完全无害:只是多跑一两次循环,不会导致数据错乱或泄漏。
真正容易出问题的是忘记更新 node->next,或者把 current 声明在循环外却没重载赋值逻辑 —— 这类错误不会报编译错,但会导致栈链断裂或 ABA 问题暴露。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











