c++语言中的原子性,在硬件上是怎么实现的

原子性(Atomicity)在硬件层面的实现,本质上是通过总线锁和缓存锁两种机制来保证的。我给你从底层到上层拆解清楚。


1. 先回顾:什么是原子性?

原子性指的是:一个操作要么全部执行完,要么完全不执行,中间不能被任何其他操作打断。

比如 i++ 在 C++ 中看似一行代码,但在硬件层面是三条指令

  1. 从内存读取 i 到寄存器

  2. 寄存器 +1

  3. 写回内存

如果两个线程同时执行 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 指令为例):

  1. 数据在缓存中:CPU 将目标变量加载到自己的 L1 缓存

  2. 标记为独占(Exclusive):通过 MESI 协议,让其他核心的该缓存行失效

  3. 执行原子操作:在缓存行内完成读-改-写

  4. 延迟写回:操作完成后,等合适时机写回内存,期间其他核心无法访问该缓存行

优势:

  • 只锁住一个缓存行(通常 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) 无效 缓存行无效,需从内存重新加载

原子操作的缓存锁流程:

  1. 核心A想执行原子操作 → 发送 RFO(Read For Ownership) 请求

  2. 其他核心收到 RFO → 将对应缓存行置为 I(无效)

  3. 核心A获得 E(独占) 状态 → 执行原子操作

  4. 操作完成后,如果修改了数据,状态变为 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,但需要你确保程序逻辑正确

 

posted @ 2026-08-27 11:22  古锁阳关  阅读(16)  评论(0)    收藏  举报