CAS(Compare-And-Swap)是一种原子操作,用于在多线程编程中实现无锁(lock-free)的线程安全操作。它的核心功能是:检查某个内存位置的值是否与预期值一致,若一致则更新为新值,否则不更新。整个过程是不可分割的原子操作,确保并发安全。
CAS 的工作原理
-
输入参数:
内存地址
:需要操作的目标内存位置。预期值(expected)
:认为该内存位置当前应该有的值。新值(new)
:若预期值匹配,则更新为的新值。
-
操作逻辑:
如果 *内存地址 == 预期值: *内存地址 = 新值 返回 true 否则: 返回 false
整个过程是原子性的,不会被其他线程中断。
CAS 的应用场景
-
无锁数据结构:
- 实现线程安全的计数器、队列、栈等,避免使用互斥锁(mutex)的性能开销。
- 示例:多个线程同时更新一个计数器:
#include <atomic> std::atomic<int> counter{0}; void increment() { int old_val = counter.load(); while (!counter.compare_exchange_weak(old_val, old_val + 1)) { // 如果失败(其他线程已修改值),重新尝试 } }
-
解决竞态条件:
- 确保某个操作(如资源分配)在并发环境下只执行一次。
-
乐观锁机制:
- 假设并发冲突少,先尝试更新,若失败则重试,避免悲观锁的阻塞。
CAS 的优缺点
优点 | 缺点 |
---|---|
无锁操作,减少线程阻塞和上下文切换。 | 可能引发忙等待(循环重试),高竞争时效率低。 |
适用于低竞争场景,性能高于互斥锁。 | ABA问题:值被其他线程从A改为B又改回A,CAS误判未修改。 |
轻量级,适合简单操作(如计数器、标志位)。 | 复杂操作难以用单个CAS实现(需配合其他机制)。 |
ABA 问题与解决方案
-
ABA问题:
线程1读取内存值为A,线程2将其改为B后又改回A。线程1的CAS操作误认为值未变,导致逻辑错误。 -
解决方案:
使用带版本号(或标签)的CAS,每次修改递增版本号,确保值的唯一性。
C++示例:struct TaggedValue { int value; int tag; // 版本号 }; std::atomic<TaggedValue> atomic_val; bool cas(TaggedValue& expected, int new_val) { TaggedValue new_struct{new_val, expected.tag + 1}; return atomic_val.compare_exchange_strong(expected, new_struct); }
C++ 中的 CAS 操作
C++通过 std::atomic
提供两种CAS函数:
-
compare_exchange_weak
:
可能在某些情况下虚假失败(如硬件架构限制),但性能更高,通常用在循环中重试。bool compare_exchange_weak(T& expected, T desired, ...);
-
compare_exchange_strong
:
严格保证成功或失败,无虚假失败,但可能更耗资源。bool compare_exchange_strong(T& expected, T desired, ...);
示例:线程安全的栈(无锁实现片段)
#include <atomic>
template<typename T>
class LockFreeStack {
struct Node {
T data;
Node* next;
};
std::atomic<Node*> head;
public:
void push(const T& data) {
Node* new_node = new Node{data, nullptr};
new_node->next = head.load();
// CAS更新头节点
while (!head.compare_exchange_weak(new_node->next, new_node));
}
bool pop(T& result) {
Node* old_head = head.load();
while (old_head &&
!head.compare_exchange_weak(old_head, old_head->next)) {}
if (!old_head) return false;
result = old_head->data;
delete old_head;
return true;
}
};
总结
- CAS 是并发编程中实现无锁操作的核心机制,通过原子性的“检查-更新”确保线程安全。
- 适用于简单、高频的低竞争场景(如计数器),但需注意ABA问题和忙等待。
- 在C++中通过
std::atomic::compare_exchange_weak/strong
使用,结合循环重试实现高效并发。