并发编程(六):Atomic 的实现——从 Runtime 到 CPU

沿着 Java AtomicInteger、Go sync/atomic 和 CPython 内部 Atomic 的真实实现路径,理解 Atomic RMW、CAS、内存顺序以及它与 Mutex 的区别。

中文发布于 更新于 2026/10/03约 4 分钟读完
并发编程(六):Atomic 的实现——从 Runtime 到 CPU

目录


0. 这一篇继续回答什么?

上一篇从语言内存模型的规则出发,看 Atomic 如何提供 Atomicity、Visibility 和 Ordering。

这一篇继续往下看:Java、Go 和 CPython 分别如何在 Runtime 和 CPU 层实现这些保证。


1. HotSpot 如何实现 AtomicInteger?

1.1 分层

先把 Java 的实现层次固定下来:

Java Source Code
实例:AtomicInteger.incrementAndGet() / compareAndSet() / get()
作用:声明 Atomic 操作
        │
        ▼
JVM Bytecode
实例:invokevirtual AtomicInteger.incrementAndGet() / compareAndSet() / get()
作用:调用 AtomicInteger 方法;没有 Atomic 专用字节码
        │
        ▼
JVM Implementation(HotSpot)
实例:Intrinsic / C1 / C2
作用:把 Atomic 操作降低到目标架构
        │
        ▼
x86-64 Hardware
实例:Atomic Instruction / Cache Coherence / Fence
作用:提供 Atomicity、Visibility 与 Ordering 的硬件基础

1.2 一次 AtomicInteger 的完整实现路径

Mermaid 图表

上面的时序图只保留跨层主流程。HotSpot 内部的核心路径可以进一步展开为:

Mermaid 图表

1.3 Runtime / Language Implementation 层的三种保证

1.3.1 Atomicity

Atomicity 对应 HotSpot 对两类 Atomic 操作的实现:

Atomic Add
  → Unsafe.getAndAddInt
  → HotSpot Intrinsic / C1 / C2
  → Atomic Get-And-Add

CAS
  → Unsafe.compareAndSetInt
  → HotSpot Intrinsic / C1 / C2
  → Atomic CAS

在 Runtime / Language Implementation 层,这些操作已经被保留为不可分割的 Atomic RMW;再继续向下,才由目标架构提供具体的原子指令。

1.3.2 Visibility 和 Ordering

Visibility 和 Ordering 对应 HotSpot 对这些 Memory Effects 的保留:

写侧:Atomic Add / CAS
读侧:volatile read

上层的 JMM / VarHandle Memory Effects 规定这些操作的内存语义,HotSpot 在编译和优化过程中必须保留相应的顺序约束。

1.4 Hardware 层的三种保证

继续向下,这些保证再由 CPU 的 Cache Coherence 和内存顺序落实。

所以 Java 与第一篇的硬件模型对应为:

Atomicity
  → Atomic Instruction
  → HotSpot / x86-64: LOCK XADD / LOCK CMPXCHG

Visibility
  → Cache Coherence
  → 典型 x86-64 CPU:MESI-family(如 MESIF / MOESI)

Ordering
  → Fence / 等价顺序约束
  → x86-64 memory ordering + LOCKed RMW / Load rules

实现可以继续查看 OpenJDK 的 AtomicInteger.java、Unsafe.java、vmIntrinsics.hpp、c1_GraphBuilder.cpp 和 c2compiler.cpp。


2. Go Runtime 如何实现 sync/atomic?

2.1 分层

Go Source Code
实例:atomic.Int64.Add() / CompareAndSwap() / Load()
作用:声明 Atomic 操作
        │
        ▼
Go Implementation
实例:sync/atomic + internal/runtime/atomic
作用:实现 Atomic API 与底层 Atomic Primitive
        │
        ▼
x86-64 Hardware
实例:Atomic Instruction / Cache Coherence / Fence
作用:提供 Atomicity、Visibility 与 Ordering 的硬件基础

2.2 一次 atomic.Int64 的完整实现路径

Mermaid 图表

上面的时序图只保留跨层主流程。Go 内部的核心路径可以进一步展开为:

Mermaid 图表

2.3 Runtime / Language Implementation 层的三种保证

2.3.1 Atomicity

Atomicity 对应 Go Implementation 中的 Atomic Primitive:

Atomic Add
  → sync/atomic
  → internal/runtime/atomic.Xadd64

CAS
  → sync/atomic
  → internal/runtime/atomic.Cas64

在 Runtime / Language Implementation 层,Add 和 CAS 已经具有不可分割的 Atomic 语义;目标架构再负责提供具体的原子指令。

2.3.2 Visibility 和 Ordering

Visibility 和 Ordering 对应 Go Implementation 对 Atomic 语义的保留:

写侧:Xadd64 / Cas64
读侧:Load64

Go Memory Model 规定 Atomic 操作的同步与顺序语义,编译器和 Runtime 必须把这些语义保留下来。

2.4 Hardware 层的三种保证

继续向下,这些保证再由 CPU 的 Cache Coherence 和内存顺序落实。

所以 Go 与第一篇的硬件模型对应为:

Atomicity
  → Atomic Instruction
  → Go / x86-64: LOCK XADDQ / LOCK CMPXCHGQ

Visibility
  → Cache Coherence
  → 典型 x86-64 CPU:MESI-family(如 MESIF / MOESI)

Ordering
  → Fence / 等价顺序约束
  → x86-64 memory ordering + LOCKed RMW / Load rules

实现可以继续查看 sync/atomic/type.go、sync/atomic/asm.s 和 internal/runtime/atomic/atomic_amd64.s。


3. CPython 如何实现内部 Atomic?

Python 标准库没有与 AtomicInteger、atomic.Int64 对称的通用整数 Atomic API,所以这一节从 CPython Runtime 内部开始。

3.1 分层

CPython Runtime Source Code
实例:Runtime 内部共享状态更新
作用:调用内部 Atomic 操作
        │
        ▼
CPython Implementation
实例:_Py_atomic_* + GCC / Clang __atomic_*
作用:实现 Atomic 与 Memory Order,并降低到目标架构
        │
        ▼
x86-64 Hardware
实例:Atomic Instruction / Cache Coherence / Fence
作用:提供 Atomicity、Visibility 与 Ordering 的硬件基础

3.2 一次 CPython Atomic 的完整实现路径

Mermaid 图表

上面的时序图只保留跨层主流程。CPython 内部的核心路径可以进一步展开为:

Mermaid 图表

3.3 Runtime / Language Implementation 层的三种保证

3.3.1 Atomicity

Atomicity 对应 CPython Implementation 对内部 Atomic 操作的表达:

Atomic Add
  → _Py_atomic_add_*
  → __atomic_fetch_add

CAS
  → _Py_atomic_compare_exchange_*
  → __atomic_compare_exchange_n

在 Runtime / Language Implementation 层,这些操作携带明确的 Atomic 语义;具体使用哪条机器指令,由编译器和目标架构继续落实。

3.3.2 Visibility 和 Ordering

Visibility 和 Ordering 对应上面流程图里的 Atomic 写入与读取边界:

写侧:Atomic Add / CAS
读侧:Atomic Load

_Py_atomic_* 将 Memory Order 传递给 GCC / Clang 的 __atomic_* builtins,并在实现层保留对应的顺序语义。

3.4 Hardware 层的三种保证

继续向下,编译器再把这些 Atomic 与 Memory Order 映射到目标架构。

所以 CPython 与第一篇的硬件模型对应为:

Atomicity
  → Atomic Instruction
  → x86-64: LOCK XADD / LOCK CMPXCHG

Visibility
  → Cache Coherence
  → 典型 x86-64 CPU:MESI-family(如 MESIF / MOESI)

Ordering
  → Fence / 等价顺序约束
  → x86-64 ordering rules + compiler-selected instruction sequence

这里描述的是 CPython Runtime 的实现,不把它提升成 Python 应用层的 Memory Model。

实现可以继续查看 pyatomic.h 和 pyatomic_gcc.h。


4. 三种实现的共同套路

看完 Java、Go 和 CPython,把具体 API 拿掉,Atomic 做的事情其实很固定。

4.1 一次更新怎么做到不可分割?

普通的 read-modify-write 是三步:

Load
  ↓
Modify
  ↓
Store

问题是其他执行单元可以插进来:

CPU A                       CPU B

Load 0                      Load 0
Add 1                       Add 1
Store 1                     Store 1

Atomic 做的事情就是把这三步收成一次操作:

Atomic RMW
   │
   ├── Add
   ├── Swap
   └── CAS

继续往下,最终由 CPU 的原子指令完成。

4.2 同时更新冲突了怎么办?

如果是 Atomic Add,两次更新都要完成:

counter = 0

CPU A                       CPU B

Atomic Add
0 → 1

                            Atomic Add
                            1 → 2

先后顺序可以不同,但不会丢掉其中一次更新。

如果是 CAS,情况不同:

CPU A                       CPU B

CAS 0 → 1                  CAS 0 → 1
    │                           │
    ▼                           ▼
  success                     failed

CAS 失败以后,上层代码决定怎么办:

CAS
 │
 ├── success ──> done
 │
 └── failed
       │
       ├── return false
       │
       └── reload → retry

所以 Atomic 没有 Mutex 那条固定的 Spin → Park → Wakeup 路径。Add 直接完成一次原子更新;CAS 失败则返回失败,是否重试由上层决定。

4.3 为什么还能保证 Visibility 和 Ordering?

Atomicity 只保证“这一笔更新不能被拆开”。

Atomic 前后的普通读写还要满足上层规定的内存顺序:

前面的普通写入
      │
      ▼
Atomic Update
      │
      ▼
Atomic Read
      │
      ▼
后面的普通读取

这条关系继续往下由三层保证:

Language / Runtime Memory Semantics
        ↓
Compiler Ordering
        ↓
Hardware Memory Ordering + Cache Coherence

所以三种实现虽然 API 不同,最终都在解决同三件事:

一次更新不可分割
        +
冲突时怎么处理
        +
前后的内存访问不能乱

4.4 Atomic 和 Mutex 有什么区别?

最简单的区别只有一句:

Atomic 处理一次共享状态操作;Mutex 保护一段代码。

4.4.1 一次状态更新,两种做法

比如只想把一个计数器加一。

Mutex:

Lock
  ↓
counter++
  ↓
Unlock

Atomic:

Atomic Add(counter, 1)

Mutex 的思路是:

先让一个执行单元进入
        ↓
再执行这段代码
        ↓
最后退出

Atomic 的思路是:

这一次更新本身
直接不可分割

如果问题真的只有一次 Add、CAS、Swap 或 Load / Store,Atomic 不需要再包一层临界区。

4.4.2 多个 Atomic 为什么不能代替一把 Mutex?

假设一次操作要同时改两个状态:

balance -= 100
count++

把两个变量分别做成 Atomic:

Atomic balance -= 100
        ↓
        ← 其他线程可能在这里读到中间状态
        ↓
Atomic count++

只能保证两次更新各自不可分割,不能保证它们合起来也是一个整体。

Mutex 可以直接把两步包起来:

Lock
  ↓
balance -= 100
count++
  ↓
Unlock

在这段临界区结束以前,其他竞争者不能进入同一段受保护代码。

所以问题一旦从“改一个状态”变成“这几步必须一起完成”,Mutex 就更容易表达。

4.4.3 怎么选?

直接看你要保护什么:

一次状态操作
Add / CAS / Swap / Load / Store
        ↓
      Atomic
一段逻辑
读取 → 判断 → 修改多个状态
        ↓
      Mutex

两者也不是完全独立的。

Mutex 自己在实现“谁先拿到锁”时,底层通常就会使用 Atomic / CAS:

Atomic / CAS
    ↓
竞争锁状态
    ↓
Mutex
    ↓
保护临界区

所以可以把关系理解成:

Atomic:解决一次共享状态更新
Mutex:在 Atomic 等底层能力之上,再提供一整段临界区

5. 下一篇:volatile

Atomic 解决的是 counter++ 这类 RMW 的原子更新。

如果不需要 RMW,只需要让一个线程的写入对另一个线程可见,并建立正确顺序,就进入了 volatile 的问题。

下一篇继续从语言规则开始讨论 Visibility 和 Ordering。

讨论

使用 GitHub 账号参与讨论,评论会保存在 GitHub Issues 中。在 GitHub 查看