CAS
CAS(Compare-And-Swap / Compare-And-Exchange,比较并交换)是并发编程中的一种原子操作,常用于实现无锁数据结构、原子计数器、自旋锁等。
在 C++ 中,CAS 主要通过 std::atomic 提供:
1 | std::atomic<T>::compare_exchange_weak(...) |
CAS 的基本思想
CAS 做的是:
如果当前值等于期望值,就把它改成新值;否则不修改,并返回失败。
可以用伪代码表示为:
1 | bool CAS(address, expected, desired) { |
注意:在 C++ 中,如果 CAS 失败,expected 会被更新为当前实际值。
C++ 中的 CAS 用法
示例:
1 |
|
执行逻辑:
1 | value == expected // 10 == 10 |
成立,所以 value 被修改为 20,CAS 返回 true。
CAS 失败时 expected 会被修改
例如:
1 | std::atomic<int> value{30}; |
因为:
1 | value != expected // 30 != 10 |
所以 CAS 失败,并且:
1 | expected == 30 |
也就是说,expected 会被更新为当前真实值。
compare_exchange_strong 和 compare_exchange_weak
C++ 提供两个版本:
1 | compare_exchange_strong |
compare_exchange_strong
语义更“强”,只要当前值等于 expected,通常就会成功。
1 | atomic.compare_exchange_strong(expected, desired); |
适合普通场景。
compare_exchange_weak
可能会出现伪失败,也就是:
即使当前值等于 expected,也可能返回 false。
这是为了适配某些 CPU 架构,提高性能。
因此 compare_exchange_weak 通常放在循环中使用:
1 | std::atomic<int> value{0}; |
不过上面这个写法有个问题:expected + 1 是基于旧表达式计算的。更常见写法是:
1 | std::atomic<int> value{0}; |
这相当于原子地执行:
1 | value++; |
当然实际中递增可以直接用:
1 | value.fetch_add(1); |
CAS 实现无锁加法示例
1 |
|
这里多个线程会竞争修改 counter:
- 线程读取旧值;
- 计算新值;
- 用 CAS 尝试写回;
- 如果期间值被别的线程改了,CAS 失败;
- 重新读取并重试。
CAS 的内存序
compare_exchange 可以指定内存序:
1 | atomic.compare_exchange_strong( |
例如:
1 | flag.compare_exchange_strong( |
常见内存序:
| 内存序 | 含义 |
|---|---|
memory_order_relaxed |
只保证原子性,不保证同步顺序 |
memory_order_acquire |
获取语义,防止后面的读写重排到前面 |
memory_order_release |
释放语义,防止前面的读写重排到后面 |
memory_order_acq_rel |
acquire + release |
memory_order_seq_cst |
最强顺序一致性,默认值 |
如果不指定,默认是:
1 | std::memory_order_seq_cst |
这是最安全但可能性能较低的选择。
CAS 的典型用途
CAS 常用于:
- 原子计数器;
- 自旋锁;
- 无锁栈、队列;
- 单例初始化;
- 状态机切换;
- 引用计数;
- 实现更高级的同步原语。
例如简单自旋锁:
1 |
|
注意这里每次失败后要重置:
1 | expected = false; |
因为 CAS 失败时 expected 会被改成当前值。
CAS 的优点
CAS 的优点:
- 原子操作,不需要互斥锁;
- 可以用于实现无锁算法;
- 避免线程阻塞和上下文切换;
- 在低竞争场景下性能很好。
CAS 的问题
ABA 问题
CAS 只检查值是否等于 expected。
假设一个变量原来是 A:
1 | 线程1读取到 A |
但实际上数据已经被修改过了。
这就是 ABA 问题。
解决方式包括:
- 使用版本号;
- 使用 tagged pointer;
- 使用
std::shared_ptr等管理生命周期; - hazard pointer;
- epoch-based reclamation。
高竞争下可能反复失败
多个线程同时 CAS 同一个变量时,只有一个能成功,其它线程会失败重试。
所以高竞争场景下 CAS 可能导致:
- CPU 空转;
- 自旋消耗;
- 性能下降。
compare_exchange_weak 可能伪失败
所以它一般放在循环中使用。
总结
CAS 是一种原子“比较并交换”操作。
在 C++ 中主要通过:
1 | std::atomic<T>::compare_exchange_weak |
使用。
核心逻辑是:
1 | if (atomic_value == expected) |
常见模式:
1 | T expected = obj.load(); |
它是无锁编程的重要基础,但需要注意:
expected会在失败时被修改;weak可能伪失败;- 需要理解内存序;
- 可能有 ABA 问题;
- 高竞争时不一定比锁更快。



