无锁算法与CAS实现原理
无锁算法概述
无锁算法(Lock-Free Algorithm)是一种在多线程编程中避免使用传统互斥锁(如mutex)的并发编程方法。它通过原子操作和内存屏障等技术保证线程安全,具有以下特点:
- 非阻塞性:至少有一个线程能够在有限步骤内完成操作
- 无死锁:不会出现线程相互等待导致系统停滞
- 高并发性:允许多个线程同时访问共享资源
典型应用场景包括:
- 高性能并发数据结构(队列、栈、哈希表等)
- 实时系统
- 高并发服务器
CAS(Compare-And-Swap)原理
CAS是Compare-And-Swap的缩写,是一种重要的原子操作指令,其伪代码如下:
bool CAS(T* ptr, T oldVal, T newVal) {
if (*ptr == oldVal) {
*ptr = newVal;
return true;
}
return false;
}
CAS操作包含三个参数:
- 内存位置(ptr):需要更新的共享变量地址
- 预期值(oldVal):认为该变量当前应该具有的值
- 新值(newVal):希望将该变量更新为的值
CAS的实现机制
硬件层面实现
现代处理器通常通过以下方式提供CAS支持:
-
x86架构:使用
CMPXCHG指令- 32位:
CMPXCHG - 64位:
CMPXCHG8B/CMPXCHG16B
- 32位:
-
ARM架构:使用
LDREX/STREX指令对 -
PowerPC架构:使用
lwarx/stwcx指令对
软件层面封装
各编程语言对CAS的封装:
- C++11:
std::atomic及其compare_exchange_weak/strong方法 - Java:
AtomicInteger等原子类的compareAndSet方法 - C#:
Interlocked.CompareExchange方法
ABA问题及解决方案
ABA问题描述
ABA问题是指:
- 线程1读取变量值为A
- 线程2将变量从A改为B,然后又改回A
- 线程1执行CAS时仍认为值是A,操作成功
这种情况可能导致逻辑错误,特别是在涉及指针操作时。
解决方案
-
版本号/标记位:在值中包含版本号或标记位
- 示例:指针+计数器组合
- Java中的
AtomicStampedReference
-
延迟回收:确保被释放的内存不会被立即重用
- 如Hazard Pointer技术
-
垃圾回收:在支持GC的语言中,对象不会被回收
无锁数据结构示例
无锁栈实现
template<typename T>
class LockFreeStack {
struct Node {
T data;
Node* next;
};
std::atomic<Node*> head;
public:
void push(const T& data) {
Node* newNode = new Node{data, nullptr};
newNode->next = head.load();
while(!head.compare_exchange_weak(newNode->next, newNode)) {
// CAS失败,重试
}
}
bool pop(T& result) {
Node* oldHead = head.load();
while(oldHead &&
!head.compare_exchange_weak(oldHead, oldHead->next)) {
// CAS失败,重试
}
if(!oldHead) return false;
result = oldHead->data;
delete oldHead;
return true;
}
};
CAS性能考虑
- 缓存一致性:CAS操作会触发缓存一致性协议(如MESI)
- 争用情况:高争用场景下可能导致大量重试
- 内存顺序:需要考虑内存屏障的影响
优化策略:
- 减少共享变量的修改频率
- 使用退避算法减少争用
- 考虑层次化设计
现代处理器对CAS的优化
- LL/SC实现:Load-Link/Store-Conditional模式
- 缓存行优化:避免错误共享(False Sharing)
- 指令重排序限制:保证内存顺序一致性
















