Compare-and-Swap:并发程序如何用一次原子比较避免互相覆盖?

TL;DR

CAS 只有在共享值仍等于预期旧值时才执行替换,是无锁数据结构的重要积木。本文解释 compare_exchange 的成功与失败路径、内存序、ABA 问题,并提醒读者无锁不等于自动安全。

CAS 只有在共享值仍等于预期旧值时才执行替换,是无锁数据结构的重要积木。本文解释 compare_exchange 的成功与失败路径、内存序、ABA 问题,并提醒读者无锁不等于自动安全。

Compare-and-Swap:并发程序如何用一次原子比较避免互相覆盖?

多个线程同时修改同一个变量时,最朴素的做法是加锁。但有些场景只需要完成一次很小的状态转换,例如把队列头从旧节点换成新节点,这时可以使用 Compare-and-Swap,简称 CAS,也常见于 compare_exchange

CAS 做的不是普通赋值

CAS 可以理解成一个原子承诺:“只有当内存里的值仍然等于我刚才看到的旧值,才把它替换成新值;如果别人已经改过,就失败并告诉我当前值。”比较和替换在一个不可被拆开的操作中完成,其他线程不会看到中间状态。

典型的无锁更新会反复尝试:先读取旧值,根据旧值计算新值,再调用 CAS。如果成功,更新完成;如果失败,说明竞争者抢先修改了状态,当前线程重新读取并计算。这个循环就是很多无锁栈、引用计数和并发容器的基本结构。

CAS 不等于“无锁就安全”

CAS 只保护一次原子状态更新,不能自动保护相关的普通字段。还要正确选择内存序,确保一个线程发布的数据能被另一个线程按预期看到。指针场景还可能遇到 ABA 问题:值看起来从 A 变成 B 又回到 A,CAS 只看到最终仍是 A,却不知道中间发生过变化。

此外,无锁不代表一定更快。高竞争下,多个线程不断失败重试,会浪费 CPU;一个设计得当的互斥锁可能更简单、更稳定。真正重要的是先判断共享状态、竞争程度和正确性要求。

AI 生成并发代码时要问什么

不要只问“能不能改成无锁”。应该要求 AI 说明原子变量保护的状态、成功与失败路径、内存序、ABA 风险、退避策略和测试方法。并发正确性不能靠读几遍代码凭感觉确认。

读者应该记住

CAS 把“读取、比较、写入”合成一次原子动作,是无锁算法的重要积木。但它只解决局部状态转换,不会替你解决内存可见性、生命周期和整体并发协议。

资料:cppreference:std::atomic

KEEP READING