std::atomic_compare_exchange_weak允许伪失败,性能更优,常用于循环重试;std::atomic_compare_exchange_strong保证仅真实不匹配时失败,适用于单次尝试场景。

什么是 std::atomic_compare_exchange_weak 和 std::atomic_compare_exchange_strong
它们是 C++ 标准库中实现 CAS 的两个核心函数,底层对应 CPU 的 cmpxchg 指令(x86)或类似原子指令(ARM 的 ldxr/stxr)。区别不在“强弱”字面意思,而在于**是否允许伪失败(spurious failure)**:weak 版本可能在值没变的情况下也返回 false,但通常生成更轻量的汇编;strong 版本保证只在值真实不匹配时才返回 false。
实际用法几乎总是在循环里配合 weak 使用——因为伪失败代价低,重试开销小;而 strong 更适合单次尝试、逻辑不允许重试的场景(比如某些状态机跃迁)。
- 必须传入指针:
expected是引用,函数内部会按需更新它;别直接传字面量或临时变量 - 返回值是
bool:true 表示交换成功(当前值 == expected 且已更新为 desired) - 如果失败,
expected被自动更新为当前内存值——这是你下一次循环的起点,别忽略它
CAS 循环为什么必须用 while 而不是 if
因为多线程环境下,从读取旧值到执行 CAS 这段时间,其他线程可能已经改过目标内存。单次 if 尝试必然漏掉竞争,导致数据覆盖或逻辑跳过。
典型错误是写成 “先读再判再换”,没包循环:
立即学习“C++免费学习笔记(深入)”;
int old = atomic_var.load();
if (old == expected) {
atomic_var.compare_exchange_strong(old, desired); // ❌ 早已失效
}
正确写法是让 expected 在循环内维持最新快照:
int expected = atomic_var.load();
do {
int desired = expected + 1;
} while (!atomic_var.compare_exchange_weak(expected, desired));
- 注意:循环体里不能无条件改
desired,否则可能无限重试(比如基于expected做复杂计算,但expected每次都在变) - 避免在循环里做耗时操作(如 malloc、锁、IO),CAS 循环应尽量轻量
- 极端高争用下,weak 可能反复伪失败,但现代 CPU 上概率极低;真遇到可切 strong 或加退避(如
std::this_thread::yield())
指针类型的 CAS 容易踩的坑:std::atomic<t></t> vs std::atomic<:shared_ptr>></:shared_ptr>
裸指针 CAS 看似简单,但极易引发 ABA 问题:指针值相同,但对象已被 delete + new 过,内容已非原物。例如链表节点被弹出又压入同一地址,CAS 误认为“没变”。
std::shared_ptr 的原子版本虽慢(涉及引用计数原子操作),但它靠控制块地址唯一性规避了大部分 ABA;而裸指针 CAS 必须手动加版本号(如 std::atomic<uintptr_t></uintptr_t> 存 ptr | (version )。
-
std::atomic<t></t>的compare_exchange只比对指针值,不管指向内容 -
std::atomic<:shared_ptr>></:shared_ptr>的 CAS 比对的是控制块地址,不是原始指针,所以更安全但有额外开销 - 不要对
std::unique_ptr做原子操作——它不可复制,标准库没提供原子特化
内存序(memory_order)选错会导致 CAS 失效
CAS 默认用 memory_order_seq_cst(最强序),安全但可能拖慢性能。若你清楚数据依赖关系,可降级——但一错就难调试。
常见组合:
- 纯计数器累加:用
memory_order_relaxed即可,只要求原子性,不要求顺序 - 发布-订阅模式(如初始化后设 flag):CAS 成功时用
memory_order_release,读 flag 时用memory_order_acquire - 绝对别在 CAS 失败分支里用比成功分支更强的内存序——这会破坏语义对称性
错误示例:compare_exchange_weak(expected, desired, memory_order_acquire, memory_order_relaxed) —— 失败路径用了 acquire,但没对应 store,属于未定义行为。
真正难的不是写对一个 CAS,而是确认整个无锁结构里所有共享访问都覆盖了正确的内存序、ABA 防护和重试逻辑。哪怕只漏一处,程序就可能在特定核数、特定调度下间歇性崩坏。









