bigywater.tech
Rust · 93

Atomic CAS 操作

CAS(比较并交换)是几乎所有无锁数据结构的基础。这篇验证它的返回值语义、怎么用循环实现任意读-改-写、手写自旋锁、经典的 ABA 问题,以及 compare_exchange_weak 在本机上到底有没有「虚假失败」。

casal
本机上 compare_exchange 与 compare_exchange_weak 都编译成的单条指令
59728 次
手写 CAS 循环 80000 次调用中因 CAS 失败而重试的次数
ABA
只比较值的 CAS 看不到 A→B→A 的中间变化

compare_exchange 的语义

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    expect 5 → 9: Ok(5)  现在 = 9
    expect 5 → 7: Err(9)  现在 = 9(期望不符,不写入,返回当前值)
    swap(1) 返回旧值 9  fetch_max(10) 返回旧值 1  现在 = 10

内存序的基础见原子类型与内存序。

CAS 循环

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    80000 次 CAS 循环调用,最终值 7096352834281527809 与单线程顺序应用 80000 次的结果相同: true
    循环体共执行 139728 次(≥ 80000 次调用,多出来的就是 CAS 失败后重试的次数)
    fetch_update(只在 <20 时加 5):Ok(10) Ok(15) Err(20)

虚假失败

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    单线程,期望值永远正确:compare_exchange_weak 虚假失败 0 / 20000000 次;compare_exchange 失败 0 次

自旋锁

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    自旋锁保护下的计数 = 400000(期望 400000)

用 Mutex 的版本见Mutex 与 RwLock。

ABA 问题

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    无版本号:CAS(期望 1) → Ok(1)(成功,但值其实被改过两次)
    有版本号:CAS(期望 值1/版本0) → false(失败,当前 值1/版本2)

汇编层面

实测 · 本机执行输出
  strong  指令: mov mov casal cmp cset ret
  weak    指令: mov mov casal cmp cset ret

分步演示

CAS
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。aarch64;release 构建
    • 没有验证:x86_64 与 ldxr/stxr 架构上 weak 的虚假失败率、完整的无锁栈/队列(需要处理内存回收)、crossbeam-epoch 等回收方案、compare_exchange 的失败序为 Acquire 时的作用、自旋锁在高争用下的公平性与 CPU 消耗
    京ICP备2026067502号-1京公网安备11010502063487号