原子(atom)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)

本文探讨了原子操作的概念及其在Intel处理器及Java中的实现方法。详细介绍了缓存行、比较并交换等术语,并分析了处理器如何通过缓存加锁或总线加锁确保原子操作。

1. 引言

原子(atom)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)意为"不可被中断的一个或一系列操作" 。在多处理器上实现原子操作就变得有点复杂。本文让我们一起来聊一聊在Intel处理器和Java里是如何实现原子操作的。

2. 术语定义

术语 英文 解释
缓存行Cache line缓存的最小操作单位
比较并交换Compare and SwapCAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。
CPU流水线CPU pipelineCPU流水线的工作方式就象工业生产上的装配流水线,在CPU中由5~6个不同功能的电路单元组成一条指令处理流水线,然后将一条X86指令分成5~6步后再由这些电路单元分别执行,这样就能实现在一个CPU时钟周期完成一条指令,因此提高CPU的运算速度。
内存顺序冲突Memory order violation内存顺序冲突一般是由假共享引起,假共享是指多个CPU同时修改同一个缓存行的不同部分而引起其中一个CPU的操作无效,当出现这个内存顺序冲突时,CPU必须清空流水线。

3. 处理器如何实现原子操作

32位IA-32处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。

3.1 处理器自动保证基本内存操作的原子性

首先处理器会自动保证基本的内存操作的原子性。处理器保证从系统内存当中读取或者写入一个字节是原子的,意思是当一个处理器读取一个字节时,其他处理器不能访问这个字节的内存地址。奔腾6和最新的处理器能自动保证单处理器对同一个缓存行里进行16/32/64位的操作是原子的,但是复杂的内存操作处理器不能自动保证其原子性,比如跨总线宽度,跨多个缓存行,跨页表的访问。但是处理器提供总线锁定和缓存锁定两个机制来保证复杂内存操作的原子性。

3.2 使用总线锁保证原子性

第一个机制是通过总线锁保证原子性。如果多个处理器同时对共享变量进行读改写(i++就是经典的读改写操作)操作,那么共享变量就会被多个处理器同时进行操作,这样读改写操作就不是原子的,操作完之后共享变量的值会和期望的不一致,举个例子:如果i=1,我们进行两次i++操作,我们期望的结果是3,但是有可能结果是2。如下图

### **原子操作Atomic Operation)比锁更高效的场景** 原子操作(如 `CAS`、`fetch_add`)通过 **硬件指令** 直接保证单步操作原子性,避免了锁的 **上下文切换、线程阻塞、缓存同步** 等开销,因此在以下场景更高效: --- ### **1. 简单共享变量操作** 当 **仅需对单个变量进行原子读写**(如计数器、标志位)时,原子操作比锁更轻量。 **示例**: ```cpp // 使用原子(高效) std::atomic<int> counter{0}; counter.fetch_add(1, std::memory_order_relaxed); // 使用锁(低效) std::mutex mtx; int counter = 0; mtx.lock(); counter++; mtx.unlock(); ``` **适用场景**: - 统计请求次数(如 `nginx` 的全局计数器)。 - 多线程下的 `bool` 标志位(如 `is_running`)。 --- ### **2. 读多写少的无锁数据结构** 在 **读操作远多于写操作** 时,无锁数据结构(基于原子操作)可避免锁的争用。 **示例**: - 无锁队列(`std::atomic` 实现生产者-消费者模型)。 - `RCU`(Read-Copy-Update,Linux 内核常用)。 **适用场景**: - 高频读取的配置信息(如路由表)。 - 实时交易系统的订单队列。 --- ### **3. 短临界区(Critical Section)** 当临界区 **仅包含1~2条指令** 时,原子操作比锁更高效。 **对比**: | **方案** | 耗时(假设) | 原因 | |----------------|-------------|--------------------| | 原子操作 | 10 ns | 单条CPU指令完成 | | 互斥锁 | 100 ns | 涉及内核态切换 | **适用场景**: - 线程安全的单例模式(`double-checked locking`)。 - 内存分配器的空闲链表管理。 --- ### **4. 高并发低竞争环境** 当 **线程竞争概率低** 时,原子操作的 `CAS` 失败少,自旋开销小。 **示例**: ```cpp // 使用CAS实现无锁栈 std::atomic<Node*> head; void push(Node* node) { node->next = head.load(); while (!head.compare_exchange_weak(node->next, node)) {} } ``` **适用场景**: - 高性能服务器(如 `Redis` 的 `dict` 扩容)。 - 游戏引擎中的实体状态更新。 --- ### **5. 锁无法使用的场景** 在 **中断上下文、信号处理函数** 中,锁可能导致死锁,必须用原子操作。 **示例**: - Linux 内核的中断处理程序(如 `spin_lock_irqsave`)。 - 实时系统(`RTOS`)的计时器回调。 --- ### **何时仍需要锁?** 1. **复杂操作**:需保护多个变量或非原子操作(如文件读写)。 2. **长时间阻塞**:如等待I/O时,锁比自旋更节省CPU。 --- ### **性能对比(伪代码)** ```cpp // 原子操作(快) atomic<int> x; x.fetch_add(1); // 互斥锁(慢) mutex m; m.lock(); x++; m.unlock(); ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值