c++oding="utf-8" ?>
根本区别在于弱版本允许虚假失败而强版本不允,弱版性能略优但需配合do-while循环使用,强版适用于必须单次尝试的场景。

std::atomic::compare_exchange_weak 和 compare_exchange_strong 有什么区别
根本区别在于:弱版本允许虚假失败(spurious failure),强版本保证只要值没变就一定成功。实际使用中,弱版本在 x86 上几乎不会虚假失败,但在 ARM/AArch64 上很常见——比如因内存屏障重排或 LL/SC 实现限制导致失败,哪怕 expected 和当前值相等。
所以别被名字误导:weak 不代表“不安全”,而是“不保证重试一次就成”。它通常生成更轻量的指令(如 ARM 的 ldxr/stxr 对),性能略好;strong 则可能隐式重试(尤其在 weak 失败时循环调用 weak),底层可能展开为带循环的序列。
- 写自旋等待逻辑时,优先用
compare_exchange_weak+ do-while 循环,这是标准推荐模式 - 只在必须“单次尝试、非重试语义”时才用
compare_exchange_strong,比如实现无锁队列的出队操作中需严格区分“值已变”和“纯失败” - 两者签名完全一致:
bool compare_exchange_weak(T& expected, T desired, memory_order success, memory_order failure),failure 必须 ≤ success
memory_order 参数选错会导致什么问题
最常踩的坑是把 memory_order_relaxed 用在需要同步的场景里——比如多个线程分别更新不同字段,但期望某次 CAS 成功后,其他线程能立刻看到之前写入的非原子变量。这时仅靠 relaxed 无法建立 happens-before 关系。
典型错误模式:
std::atomic<int> flag{0};
int data = 42;
// 线程 A
data = 100;
flag.compare_exchange_strong(expected=0, desired=1,
std::memory_order_relaxed,
std::memory_order_relaxed); // ❌ data 修改对线程 B 不可见
</int>
正确做法是至少用 memory_order_release(写端)配 memory_order_acquire(读端):
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
// 线程 A
data = 100;
flag.compare_exchange_strong(expected=0, desired=1,
std::memory_order_release,
std::memory_order_relaxed);
// 线程 B
if (flag.load(std::memory_order_acquire) == 1) {
// 此时 data == 100 一定可见
}
- 初学者可默认用
std::memory_order_acq_rel(对 CAS 本身),兼顾读写同步需求 - 若只关心原子变量自身顺序,且无其他共享数据依赖,
relaxed是安全的 - 注意:failure 的 memory_order 必须不能比 success 更强(如 success=acq_rel 时,failure 只能是
relaxed或acquire)
compare_exchange 为什么总要传引用给 expected
因为函数内部会根据比较结果决定是否修改 expected 的值:如果失败,expected 会被自动更新为当前实际值。这个设计是为了方便后续重试——你不用手动 reload,直接拿更新后的 expected 再试一次就行。
错误写法(值传递,改了也没用):
int expected = 0; flag.compare_exchange_weak(expected, 1); // expected 是副本,失败后原变量不变 // 下次循环还是拿 0 去比,永远卡死
正确写法(引用传递,自动刷新):
int expected = 0;
do {
if (flag.compare_exchange_weak(expected, 1)) {
break;
}
// 这里 expected 已被设为 flag 当前值,下次循环自然重试
} while (expected == 0);
- 务必声明
expected为局部变量(不能是全局或 static),否则多线程并发时会相互覆盖 - 如果想避免循环,又需要知道当前值,可用
load()先读一次,但要注意两次操作之间存在竞态窗口 - 对指针类型(如
std::atomic<node></node>),expected必须是指针引用(Node*&),不是Node**
在结构体或类上用 compare_exchange 需要特别注意什么
直接对 std::atomic<mystruct></mystruct> 调用 compare_exchange 是合法的,但前提是 MyStruct 是 trivially copyable,且大小不超过平台支持的原子操作上限(通常 ≤ 16 字节,x86-64 下最大支持 16B,ARM64 一般也是 16B)。超过则退化为锁实现(libstdc++/libc++ 会用内部互斥量),完全失去 lock-free 意义。
常见陷阱:
- 含虚函数、非 trivial 构造/析构、std::string 成员的 struct 无法用于
std::atomic<t></t> - 即使满足 trivially copyable,也要检查
std::atomic<t>::is_always_lock_free</t>,运行时用.is_lock_free()确认 - 结构体内有 padding 或未对齐字段,可能导致 CAS 失败(字节级比较不等),建议用
static_assert(std::is_standard_layout_v<t>)</t>+ 显式#pragma pack(1)控制布局 - 更稳妥的做法是只对整数、指针等基础类型做 CAS,复杂状态用指针原子交换(如
std::atomic<node></node>)+ RCUs 或 hazard pointers 管理生命周期
真正麻烦的从来不是语法怎么写,而是预期中的“强一致性”在特定架构、特定内存序、特定结构体布局下悄悄失效。每次加 CAS,最好顺手跑一遍 TSAN,再在 ARM 板子上压测一小时。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!








