文章目录
原子操作
1 定义(WHAT)
原子操作(Atomic Operation)是指一个操作在 执行过程中不会被其他线程或系统事件(如中断、上下文切换)打断,要么完全执行完毕,要么完全不执行,不存在中间状态。
原子操作是并发编程中保证数据一致性的核心机制。
2 作用(WHY)
在多线程或多核环境中,若操作不具备原子性,可能引发以下问题:
- 竞态条件(Race Condition):多个线程同时修改共享数据,导致结果不可预测。
- 数据不一致:如银行转账操作中,扣款和入账若被中断,可能导致金额错误。
- 逻辑错误:如计数器递增操作被中断,导致计数丢失。
3 原子性如何实现?(HOW)
原子性依赖于 硬件支持 和 操作系统协作,以下是关键知识点:
3.1 硬件支持
- 原子指令:CPU 提供专用的原子指令(如
CAS
,XCHG
,LOCK
前缀指令),在单个指令周期内完成内存读写操作。- 示例:x86 的
CMPXCHG
(比较并交换)指令是原子操作。
- 示例:x86 的
- 总线锁(Bus Locking):
- 在多核 CPU 中,通过锁定总线(或缓存行)确保指令执行期间其他核心无法访问同一内存地址。
- 现代 CPU 使用 缓存一致性协议(如 MESI) 替代总线锁,通过标记缓存行状态(Modified/Exclusive/Shared/Invalid)实现高效同步。
3.2 操作系统支持
- 中断屏蔽:在单核系统中,内核态代码可通过关闭中断(如
cli
指令)临时屏蔽外部中断,确保原子性。 - 原子性系统调用:操作系统提供原子性 API(如 Linux 的
atomic_t
类型)。
3.3 编程语言支持
- 原子类型:如 C++ 的
std::atomic
、Java 的AtomicInteger
,通过封装硬件原子指令提供跨平台原子操作。std::atomic<int> counter(0); counter.fetch_add(1); // 原子递增
4 原子操作的不可中断性
4.1 单核环境
- 禁止抢占:通过关闭中断或禁用调度,确保当前线程独占 CPU 直到操作完成。
- 示例:Linux 内核的
spin_lock_irqsave
在获取锁时关闭中断。
- 示例:Linux 内核的
4.2 多核环境
- 缓存锁与总线锁:通过 CPU 的原子指令锁定缓存行或总线,阻止其他核心访问同一内存地址。
- MESI 协议:确保多核间缓存一致性,避免脏读/写。
4.3 示例分析
假设线程 A 执行原子递增操作:
- CPU 加载
counter
值到寄存器。 - 寄存器值加 1。
- 通过原子指令(如
LOCK XADD
)将新值写回内存。
在此过程中,其他线程无法插入修改 counter
,即使发生线程调度,操作也会在恢复后继续完成。
5 原子操作的应用场景
场景 | 说明 |
---|---|
无锁数据结构 | 如无锁队列、栈、计数器,依赖 CAS 实现线程安全。 |
同步原语 | 自旋锁、信号量的底层实现。 |
资源计数 | 统计并发请求数、连接数,避免锁竞争。 |
内存管理 | 引用计数(如智能指针 std::shared_ptr 的原子计数)。 |
6 非原子操作的后果
若操作不满足原子性,典型问题如下:
- 丢失更新(Lost Update):
// 非原子操作:两个线程并发执行 counter++ int counter = 0; void increment() { int tmp = counter; // 线程A读取0,线程B读取0 tmp = tmp + 1; // 均计算为1 counter = tmp; // 最终counter=1,而非预期2 }
- 数据损坏:如链表插入节点时被中断,导致指针指向无效内存。
7 知识点
知识点 | 说明 |
---|---|
原子指令 | CPU 提供的不可分割指令(如 CAS, XCHG)。 |
总线锁与缓存锁 | 多核环境下实现原子性的硬件机制。 |
竞态条件 | 非原子操作导致的数据不一致问题。 |
内存屏障 | 确保指令执行顺序,避免编译器/CPU 重排破坏原子性(如 std::memory_order )。 |
操作系统协作 | 中断屏蔽、原子系统调用支持。 |
8 总结
原子操作的“不可中断”特性由硬件指令和操作系统机制共同保障,是并发编程中避免数据竞争的核心手段。理解原子性需结合 CPU 架构、内存模型及编程语言特性,合理使用原子类型和无锁数据结构可显著提升多线程程序性能与正确性。