目录
一、原子操作(Atomic Operations)
1. 基本概念
原子操作是指 不可分割的操作,在多线程环境中,其他线程无法观察到该操作的中间状态。原子操作的核心特性是:
- 原子性:操作要么全部完成,要么完全不执行。
- 不可中断性:操作过程中不会被线程调度或中断打断。
- 可见性:操作结果对其他线程立即可见。
常见原子操作类型:
- 原子读/写:直接读取或修改变量的值。
- 原子加减:如
fetch_add
、fetch_sub
。 - 比较并交换(CAS):
Compare-And-Swap
,核心无锁算法工具。 - 加载-存储链接(LL/SC):如 ARM 架构的
Load-Link/Store-Conditional
。
2. 实现方式
(1)硬件支持
- CPU 指令:现代处理器提供原子指令(如 x86 的
CMPXCHG
、ARM 的LDREX/STREX
)。 - 内存屏障:确保操作顺序性和可见性(如
memory barrier
)。
(2)语言/库支持
- C/C++:
std::atomic
(C++11 起)。 - Java:
AtomicInteger
、AtomicReference
等。 - C#:
Interlocked
类。 - Python:
threading.Lock
(非原子操作,但可模拟原子性)。
3. 应用场景
- 计数器:如统计访问次数。
- 标志位:控制线程状态(如
is_running
)。 - 无锁数据结构:队列、栈、哈希表等。
二、无锁编程(Lock-Free Programming)
1. 基本概念
无锁编程是一种 不依赖传统锁机制 的并发编程范式,通过 原子操作 和 乐观锁策略 实现线程安全。其核心思想是:
- 避免阻塞:线程不会因等待锁而挂起。
- 重试机制:通过循环和 CAS 操作不断尝试更新共享资源。
2. 核心特点
特性 | 描述 |
---|---|
无需锁 | 不依赖 mutex 、semaphore 等锁机制。 |
减少上下文切换 | 避免线程阻塞和唤醒的开销,降低延迟。 |
避免死锁 | 不需要处理锁的获取顺序,不存在死锁风险。 |
复杂性高 | 算法设计和调试难度较大,需处理 ABA 问题、活锁等。 |
3. 核心工具:CAS(Compare-And-Swap)
(1)原理
CAS 是无锁编程的核心技术,包含三个参数:
- 内存位置(V):目标变量的地址。
- 预期值(E):期望的当前值。
- 新值(N):要更新的值。
操作逻辑:
if (V == E) {
V = N;
return true;
} else {
return false;
}
整个过程是原子的,由 CPU 指令直接支持。
(2)伪代码示例
bool compare_exchange(T* V, T E, T N) {
if (*V == E) {
*V = N;
return true;
}
return false;
}
(3)ABA 问题
- 问题:当值从
A → B → A
时,CAS 误判为未修改。 - 解决方案:
- 版本号(Tag):附加一个递增的版本号(如
AtomicStampedReference
)。 - 双字操作:同时比较值和版本号。
- 版本号(Tag):附加一个递增的版本号(如
4. 无锁数据结构示例
(1)无锁栈(Lock-Free Stack)
C++ 实现:
typedef struct Node {
int value;
struct Node* next;
} Node;
typedef struct {
atomic_ptr(Node*) head; // 原子指针
} LockFreeStack;
// 初始化栈
void stack_init(LockFreeStack* stack) {
atomic_init(&stack->head, NULL);
}
// 入栈操作
void stack_push(LockFreeStack* stack, int value) {
Node* new_node = (Node*)malloc(sizeof(Node));
new_node->value = value;
Node* current_head;
do {
current_head = atomic_load(&stack->head);
new_node->next = current_head;
} while (!atomic_compare_exchange_weak(&stack->head, ¤t_head, new_node));
}
// 出栈操作
int stack_pop(LockFreeStack* stack) {
Node* current_head;
Node* new_head;
do {
current_head = atomic_load(&stack->head);
if (current_head == NULL) return -1; // 栈空
new_head = current_head->next;
} while (!atomic_compare_exchange_weak(&stack->head, ¤t_head, new_head));
int value = current_head->value;
free(current_head);
return value;
}
(2)无锁队列(Lock-Free Queue)
C++ 实现(基于 CAS):
#include <atomic>
#include <memory>
template <typename T>
class LockFreeQueue {
private:
struct Node {
T data;
std::shared_ptr<Node> next;
Node(T val) : data(val), next(nullptr) {}
};
std::atomic<std::shared_ptr<Node>> head;
std::atomic<std::shared_ptr<Node>> tail;
public:
LockFreeQueue() {
auto dummy = std::make_shared<Node>(T{});
head.store(dummy);
tail.store(dummy);
}
void enqueue(T value) {
auto newNode = std::make_shared<Node>(value);
std::shared_ptr<Node> oldTail, prev;
do {
oldTail = tail.load();
prev = oldTail;
} while (!tail.compare_exchange_weak(oldTail, newNode));
prev->next = newNode;
}
bool dequeue(T& result) {
std::shared_ptr<Node> oldHead = head.load();
std::shared_ptr<Node> oldTail = tail.load();
std::shared_ptr<Node> next = oldHead->next;
if (next == nullptr) return false;
result = next->data;
return head.compare_exchange_strong(oldHead, next);
}
};
5. 无锁编程 vs 传统锁机制
特性 | 无锁编程 | 传统锁机制 |
---|---|---|
并发性 | 高(无阻塞,减少上下文切换) | 低(线程可能阻塞) |
实现复杂度 | 高(需处理 ABA、活锁等问题) | 低(逻辑简单) |
性能 | 高(适合短时间操作) | 低(锁竞争导致延迟) |
适用场景 | 读多写少、高性能要求场景 | 通用场景 |
调试难度 | 高(需验证原子性和内存顺序) | 低(逻辑直观) |
三、无锁编程的挑战与优化
1. 常见问题
- ABA 问题:通过版本号或双字操作解决。
- 活锁(Livelock):线程不断重试失败,浪费 CPU 资源。
- 内存泄漏:无锁队列中旧节点可能无法释放(需引入垃圾回收机制)。
2. 优化策略
- 指数退避:重试失败时延迟重试,减少竞争。
- 内存顺序控制:选择合适的内存语义(如
relaxed
、acquire/release
)。 - 混合锁机制:对复杂操作使用锁,对简单操作使用无锁。
四、内存顺序与性能优化
1. 内存顺序模型
- Relaxed(松散):仅保证原子性,不保证顺序。
- Acquire/Release:保证读写顺序(适用于锁实现)。
- Sequentially Consistent:严格顺序一致性(性能代价高)。
C++ 示例:
std::atomic<int> flag = 0;
// 写线程
void writer() {
data = 42;
flag.store(1, std::memory_order_release); // release 语义
}
// 读线程
void reader() {
int local_flag = flag.load(std::memory_order_acquire); // acquire 语义
if (local_flag == 1) {
// data 已经可见
std::cout << data << std::endl;
}
}
2. 性能优化
- 减少原子操作范围:仅对关键共享变量使用原子操作。
- 批量处理:合并多个操作以减少 CAS 调用次数。
- 硬件特性利用:根据 CPU 架构选择合适的原子指令。
五、无锁编程的适用场景
场景 | 适用性 |
---|---|
高性能服务器 | 高频交易、实时消息队列等需低延迟的场景。 |
缓存系统 | 读多写少的缓存更新(如 LRU 缓存)。 |
分布式系统 | 局部状态管理(如节点间同步)。 |
嵌入式系统 | 有限资源下减少锁开销(如 RTOS)。 |
六、总结
- 原子操作 是无锁编程的基础,通过硬件指令和语言支持实现线程安全。
- CAS 技术 是无锁算法的核心工具,需注意 ABA 问题和内存顺序。
- 无锁编程 在高性能场景中具有显著优势,但设计和调试复杂度较高。
- 内存顺序控制 和 优化策略 是提升无锁程序性能的关键。