compare_exchange_weak 允许伪失败(值相等却返回 false),适合循环 CAS;compare_exchange_strong 仅在值不等时失败,适用于单次尝试场景。

compare_exchange_strong 和 compare_exchange_weak 的区别在哪
这两个函数都用来做原子的“比较并交换”,但行为不同:compare_exchange_strong 保证失败只发生在值不相等时;compare_exchange_weak 允许“伪失败”(即值相等却返回 false),常见于某些硬件平台(如 ARM、旧 x86)的 LL/SC 实现。实际写循环 CAS 时,compare_exchange_weak 更常用,因为它在多数平台上性能略好,且伪失败可被循环捕获。
用错的典型表现是:只调用一次 compare_exchange_weak 就放弃,结果逻辑出错——它本就该放在 while 循环里重试。
- 需要强语义保证(比如仅尝试一次、失败就走备选路径)→ 用
compare_exchange_strong - 实现自旋锁、无锁栈、计数器等常规 CAS 循环 → 优先用
compare_exchange_weak - 编译器对
weak版本可能生成更紧凑的指令,尤其在 ARM64 上
参数顺序和 memory order 容易写反
compare_exchange_weak 的签名是:bool compare_exchange_weak(T& expected, T desired, memory_order success, memory_order failure)。最常踩的坑是把 expected 当成“当前要设的值”,其实它是“你期望当前变量所持有的值”——函数会拿这个去跟原子变量真实值比,相等才替换为 desired;不等则把原子变量当前值写回 expected(注意:这是个引用参数!)。
另一个坑是 memory order 乱配。比如写成 memory_order_relaxed 成功路径 + memory_order_seq_cst 失败路径,编译器会报错:failure order 不能比 success order 更强。合法组合是 failure ≤ success(例如 success=memory_order_acq_rel,failure 可取 memory_order_acquire 或更弱)。
-
expected必须是左值引用,且调用后可能被修改(失败时写入当前真实值) - 如果只关心同步语义不关心性能,统一用
memory_order_seq_cst最省心 - 在无锁数据结构中,成功路径常用
memory_order_acq_rel,失败路径用memory_order_acquire
一个安全的自增计数器示例
下面这段代码实现线程安全的原子自增,展示了典型循环模式和参数用法:
std::atomic<int> counter{0};
int increment() {
int expected = counter.load(std::memory_order_relaxed);
while (!counter.compare_exchange_weak(expected, expected + 1,
std::memory_order_acq_rel,
std::memory_order_acquire)) {
// expected 已被更新为 counter 当前值,直接重试
}
return expected + 1;
}
</int>
关键点:循环体里不做额外计算,只更新 expected 后重试;load 用 relaxed 是因为后续 CAS 本身带 acquire 语义;返回值是“自增后的值”,不是旧值。
- 别在循环里重新生成
desired(比如写成expected++再传进去),会导致竞态 - 如果函数需返回旧值,直接返回
expected即可 - 避免在循环内 sleep 或 yield——这会让 CAS 变成“假自旋”,失去无锁意义
调试时看不到 expected 被修改?检查是否传了右值
常见错误写法:counter.compare_exchange_weak(42, 43) —— 编译不过,因为 42 是字面量(右值),无法绑定到 T& expected。更隐蔽的是临时对象,比如 int x = get_init_value(); counter.compare_exchange_weak(x, x+1) 看似没问题,但如果 get_init_value() 返回的是 const int& 或临时 int,也可能触发绑定失败或未定义行为。
调试时发现 expected 没变,大概率是传参时用了字面量、函数返回的临时量,或者声明成了 const int。必须确保它是可修改的左值,且生命周期覆盖整个 CAS 调用及后续循环。
- 永远用普通局部变量接住初始值,比如
int expected = counter.load(); - 不要对
expected加const修饰 - Clang/GCC 在 -Wall 下会对右值绑定到非 const 引用报错,别忽略这类警告
expected 不是目标值,是你赌它“此刻应该等于什么”。这个直觉没建立起来,代码就会在并发下间歇性崩。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











