c++语言中的原子性,在硬件上是怎么实现的
原子性(Atomicity)在硬件层面的实现,本质上是通过总线锁和缓存锁两种机制来保证的。我给你从底层到上层拆解清楚。
1. 先回顾:什么是原子性?
原子性指的是:一个操作要么全部执行完,要么完全不执行,中间不能被任何其他操作打断。
比如 i++ 在 C++ 中看似一行代码,但在硬件层面是三条指令:
-
从内存读取
i到寄存器 -
寄存器 +1
-
写回内存
如果两个线程同时执行 i++,可能发生:
-
线程A读取
i=5 -
线程B读取
i=5(A还没写回) -
线程A写回
i=6 -
线程B写回
i=6(丢失了一次增加)
原子操作就是保证这三步不可分割,要么全做,要么不做。
2. 硬件层面的两种实现机制
机制一:总线锁(Bus Lock)—— 最粗暴的方式
原理:
CPU 在执行原子操作时,通过 LOCK# 信号引脚拉低,锁住系统总线(CPU 和内存之间的通道)。
效果:
-
在这条指令执行期间,其他 CPU 核心无法访问内存
-
相当于整个系统暂停了内存访问
缺点:
-
性能极差!因为锁总线会阻塞所有核心的内存访问
-
现代 CPU 几乎不再使用(除非必要)
历史背景:
Intel 早期的 80486 及之前的 CPU,LOCK 前缀就是锁总线。
机制二:缓存锁(Cache Lock)—— 现代 CPU 的主流
原理:
利用 CPU 的缓存一致性协议(如 MESI),将原子操作锁在单个缓存行(Cache Line)内,而不是锁整个总线。
工作流程(以 x86 的 LOCK CMPXCHG 指令为例):
-
数据在缓存中:CPU 将目标变量加载到自己的 L1 缓存
-
标记为独占(Exclusive):通过 MESI 协议,让其他核心的该缓存行失效
-
执行原子操作:在缓存行内完成读-改-写
-
延迟写回:操作完成后,等合适时机写回内存,期间其他核心无法访问该缓存行
优势:
-
只锁住一个缓存行(通常 64 字节),不影响其他内存访问
-
性能远高于总线锁
什么时候会降级为总线锁?
-
数据不在缓存中(需要从内存读取)
-
缓存行跨越多个缓存行(未对齐的内存地址)
-
CPU 不支持缓存锁(极老型号)
3. C++ 代码是如何映射到硬件的?
#include <atomic>
std::atomic<int> counter{0};
void increment() {
counter.fetch_add(1, std::memory_order_relaxed);
}
底层汇编(x86-64):
lock add DWORD PTR [counter], 1
-
lock前缀告诉 CPU:这个操作需要原子性 -
CPU 根据情况选择缓存锁或总线锁
注意: 在某些简单情况下(比如对齐的 4 字节整数加减),x86 CPU 本身保证原子性,不需要 lock 前缀。但为了跨平台安全,C++ 标准要求使用 std::atomic。
4. 不同 CPU 架构的实现差异
| CPU 架构 | 实现方式 | 特点 |
|---|---|---|
| x86/x64 | 使用 LOCK 前缀,优先缓存锁 |
强内存模型,原子操作支持完善 |
| ARM | 使用 LDREX / STREX 指令对(加载-链接/条件存储) |
弱内存模型,需要内存屏障配合 |
| RISC-V | 使用 LR / SC 指令对 |
类似 ARM,由软件控制重试 |
ARM 的例子(简化):
loop:
LDREX r0, [addr] ; 加载并标记独占
ADD r0, r0, #1
STREX r1, r0, [addr] ; 尝试存储,如果被其他核心修改则失败
CMP r1, #0
BNE loop ; 失败则重试
这里没有 LOCK 前缀,而是通过独占监视器(Exclusive Monitor)来检测竞争。
5. 核心硬件组件:缓存一致性协议(MESI)
缓存锁能工作,完全依赖MESI 协议:
| 状态 | 含义 | 说明 |
|---|---|---|
| M(Modified) | 已修改 | 缓存行被修改,与内存不一致,且独占 |
| E(Exclusive) | 独占 | 缓存行与内存一致,且只有本核心有 |
| S(Shared) | 共享 | 缓存行与内存一致,多个核心共享 |
| I(Invalid) | 无效 | 缓存行无效,需从内存重新加载 |
原子操作的缓存锁流程:
-
核心A想执行原子操作 → 发送 RFO(Read For Ownership) 请求
-
其他核心收到 RFO → 将对应缓存行置为 I(无效)
-
核心A获得 E(独占) 状态 → 执行原子操作
-
操作完成后,如果修改了数据,状态变为 M(已修改)
6. 总结与关键点
| 层级 | 实现方式 |
|---|---|
| C++ 层面 | 使用 std::atomic + memory_order |
| 编译器层面 | 生成带 LOCK 前缀(x86)或 LDREX/STREX(ARM)的指令 |
| CPU 层面 | 优先用缓存锁(MESI 协议),必要时降级为总线锁 |
| 硬件电路 | 通过缓存一致性协议和内存屏障保证可见性和原子性 |
一个很重要的常识:
-
原子性 ≠ 可见性:原子操作保证操作不可分割,但其他核心何时能看到修改,由内存序(
memory_order)控制 -
C++ 的
std::atomic默认是memory_order_seq_cst(顺序一致性),性能最差但最安全 -
如果追求极致性能,可以用
memory_order_relaxed,但需要你确保程序逻辑正确
浙公网安备 33010602011771号