Skip to content

比较并交换

Compare-and-swap · CAS

原子地比较内存值并在相等时写入新值、同时返回比较结果的读改写原语。

条目类型
模型

形式陈述

比较并交换(CAS)可视为只有一种条件更新方法的并发对象;它是作用于共享位置 x读改写原语:给定期望值 a 与新值 b,它在一个原子操作内完成条件更新——

CAS(x,a,b):{xb, 返回成功(或旧值 a,若当前 x=a,x 不变, 返回失败(或当前旧值),否则.

比较与写入之间不存在其他线程可插入的窗口,这是它区别于"先读后写"两步序列的全部要点。定义只约束该位置上值的相等性判断与条件写入;跨位置的内存排序(如 acquire/release 语义)由具体硬件或语言接口另行规定,不属于 CAS 本身。

直觉

CAS 把乐观并发的哲学压缩进一条指令:线程不加锁地读取共享状态、在私有空间算出新状态,提交时声明"仅当世界仍是我看到的样子才生效"。若其他线程已抢先修改,提交失败,线程重读重算再试。与悲观的锁相比,它把"防止别人插手"换成了"检测别人是否插过手":无竞争时零等待,有竞争时以重试为代价。这个原语为非阻塞进展提供了工具,但进展性属于整个算法,而不属于 CAS 本身;在所有更新都经 CAS 提交的 lock-free 算法里,一次失败可作为其他线程已改变共享状态的证据,而用 CAS 实现的自旋锁仍可能在持锁线程停顿时卡住所有等待者。

CAS 竞争中的单次成功
例子与边界

正例是无锁计数器:线程读出 v,执行 CAS(x,v,v+1),失败则重试。两个线程同时从 v=5 出发,只有一个 CAS 成功把 x 变为 6,另一个发现 x5 而失败、重读到 6、再 CAS 到 7——两次自增都不丢失。对照组是普通的"读、加一、写回":两个线程都读到 5、都写回 6,一次更新凭空蒸发,这正是 CAS 的条件检查所堵住的漏洞。

无锁栈的压入更能看出 CAS 如何保护复合结构:线程先读取旧栈顶,把新节点的 next 指向旧顶,再尝试把栈顶从旧值换成新节点;失败说明这段时间里栈已经变化,必须重读后重做。成功的 CAS 通常就是线性化一致性证明中的线性化点,整个压入操作可视为在这一瞬间生效。

边界之一是 ABA 问题:CAS 判断的是"值相等"而非"未被动过"。若 xA 改为 B 又改回 A,中间的变化对 CAS 完全隐形;在无锁栈中,这可能让指针指向已被弹出又重用的节点,造成结构损坏。版本标签可让相同指针的两代取值不同;hazard pointer、epoch 等安全内存回收机制能防止节点过早释放和地址重用,却不会自动消除所有逻辑上的 ABA,仍需结合数据结构语义分析。边界之二是进展性等级:正确设计的 CAS 重试环常能达到 lock-free 而非 wait-free——系统整体持续完成操作,但个别倒霉线程可能次次落败、无限重试,需要额外的帮扶机制才能升级到 wait-free。

推论与应用

CAS 是现代同步设施的枢纽原语:无锁栈与队列、原子引用、一次性初始化、自旋锁与其他互斥实现都以它为骨架,主流指令集与并发库均直接提供。CAS 在标准原子语义下具有无限共识数;这项分类、通用构造与低层对象不可能性统一由共识数与 Wait-free 层级说明,本页只保留 CAS 的操作规格、算法用法与 ABA 边界。

lock-free 进展的大量正面结果依赖 CAS 或同等强度原语,但一条 CAS 指令本身不赋予整个调用路径任何进展等级。重试策略、帮助机制和内存回收都必须一并分析。

参考资料
  • Maurice Herlihy and Nir Shavit, The Art of Multiprocessor Programming, rev. 1st ed., Morgan Kaufmann, 2012,Chs. 1–18。
  • Hagit Attiya and Jennifer Welch, Distributed Computing: Fundamentals, Simulations, and Advanced Topics, 2nd ed., Wiley, 2004,Chs. 1–18。
关系图谱6 个相邻概念 · 2 类关系

拖动节点调整位置。

显示关系

显示:依赖

  1. 前置三跳
  2. 前置二跳
  3. 前置一跳
  4. 当前条目
  5. 后续一跳
  6. 后续二跳
  7. 后续三跳
文字版关系按与当前条目的最短距离分组
分类位置

上位 / 更一般

下位 / 直接特例

暂未标注直接特例。