目录
- 1. 核心定义
- 2. 两种主要的共享内存架构
- a) 均匀内存访问
- b) 非均匀内存访问
- 3. 共享内存多处理器的核心挑战与解决方案
- a) 缓存一致性
- b) 内存一致性
- 4. 编程模型与同步
- 5. 优势与劣势
- 优势:
- 劣势:
- 总结
共享内存多处理器是多处理器系统中最常见和直观的一种架构,也是现代多核CPU设计的核心范式。
1. 核心定义
共享内存多处理器是指所有处理器(或处理器核心)都共享一个统一的全局物理内存空间的计算机系统。系统中的任何一个处理器都能够直接访问任何内存地址,而无需其他处理器的协助。
关键特征:
- 单一全局地址空间:所有处理器看到的是同一块内存,每个内存地址都是唯一的。
- 通过读写内存进行通信:处理器之间通过在共享内存中读取和写入数据来进行通信和协作。例如,处理器A将结果写入内存的某个位置,处理器B再从该位置读取。
- 对称多处理(SMP):这是共享内存多处理器最常见的形态。在SMP系统中,所有处理器在硬件上是对称的( identical ),它们对内存、I/O设备等所有资源的访问权限和访问时间都是平等的。没有任何一个处理器是主控器。
2. 两种主要的共享内存架构
根据处理器访问内存的延迟是否一致,共享内存系统可分为两类:
a) 均匀内存访问
- 英文:Uniform Memory Access
- 工作原理:所有处理器通过一条共享总线或一个交叉开关连接到同一个集中式的内存模块。无论哪个处理器访问内存,其延迟都是相同(均匀)的。
- 优点:架构简单,易于实现。
- 缺点:可扩展性极差。处理器数量增加时,共享总线会成为巨大的瓶颈,因为所有内存请求都要挤过这条“独木桥”,访问延迟会急剧增加。
- 示意图:
[CPU₁] [CPU₂] ... [CPUₙ] // 多个对称的处理器| | |└─────┴───BUS──┴───── // 共享总线(瓶颈)|[MEM] // 共享的集中式内存
- 例子:早期的多处理器服务器(如双Pentium Pro系统),现代的单片多核处理器(单个CPU芯片上的多个核心共享最后一级缓存和内存控制器)在芯片内部可视为UMA。
b) 非均匀内存访问
- 英文:Non-Uniform Memory Access
- 工作原理:这是为克服UMA的可扩展性瓶颈而发展出的架构。在NUMA系统中:
- 每个处理器(或一组处理器)拥有本地内存,这部分内存的访问速度非常快(延迟低)。
- 处理器也可以访问其他处理器的远程内存,但访问速度较慢(延迟高)。
- 所有处理器的本地内存共同构成了一个统一的全局地址空间。对软件来说,它仍然像一块大内存,但硬件负责将访问路由到正确的物理位置。
- 优点:可扩展性极佳。可以通过互连网络(如AMD的Infinity Fabric,Intel的QPI/UPI)连接大量处理器节点,构建非常庞大的系统。
- 缺点:编程复杂性高。如果程序大量访问远程内存,性能会急剧下降。需要进行“NUMA感知”的优化,尽量让数据和执行它的线程位于同一个NUMA节点内。
- 示意图:
[CPU₀ + Mem₀] <─────> [Interconnect] <─────> [CPU₁ + Mem₁] // 两个NUMA节点︎(节点0,本地内存快) (如Infinity Fabric) (节点1,远程内存慢)
- 例子:几乎所有现代的多路服务器(如双路、四路AMD EPYC或Intel Xeon服务器)都是NUMA架构。单个AMD Ryzen/EPYC或Intel Core-X芯片内部也是NUMA架构。
3. 共享内存多处理器的核心挑战与解决方案
a) 缓存一致性
这是共享内存系统最核心的硬件问题。
- 问题:每个处理器都有自己的私有缓存(L1/L2 Cache)来加速内存访问。如果处理器A修改了其缓存中的某个数据(例如,变量
X=5
),而处理器B的缓存中仍然保留着该数据的旧副本(X=4
),那么当B去读取X
时,就会得到错误的值。这就是缓存不一致。 - 解决方案:缓存一致性协议。由硬件自动维护,对软件完全透明。
- 最著名的协议:MESI
- M (Modified):缓存行是脏的,与主内存不一致,只有本缓存有副本。
- E (Exclusive):缓存行是干净的,与主内存一致,只有本缓存有副本。
- S (Shared):缓存行是干净的,与主内存一致,可能有多个缓存有副本。
- I (Invalid):缓存行数据是无效的,不能使用。
- 工作原理:当某个处理器要写入一个数据时,一致性协议会通过总线嗅探或目录协议等方式,通知所有其他缓存中持有该数据副本的处理器,将它们对应的缓存行状态置为无效。这样,其他处理器在下次读取时就必须从主内存或修改者的缓存中重新获取最新数据。
- 最著名的协议:MESI
b) 内存一致性
这是给程序员和编译器的模型问题。
- 问题:多个处理器并发地读写内存,何种顺序的内存操作结果是合法的?例如,处理器A先写变量
X
再写Y
,处理器B看到的结果是否一定是Y
的新值和X
的新值?(不一定!) - 解决方案:内存一致性模型。它定义了共享内存系统的行为规范,规定了写入操作何时并对其他处理器可见。
- 顺序一致性:最直观的模型。要求任何执行结果都等同于所有处理器的操作按某个顺序顺序执行,且每个处理器的操作顺序都符合其程序顺序。性能差,但易于理解。
- 松弛一致性模型(如x86的TSO,ARM的弱内存模型):为了性能而放松了对操作顺序的严格要求,允许硬件和编译器进行重排序。这就要求程序员在需要严格顺序的地方(如锁、信号量),使用内存屏障指令来显式地强制排序。
4. 编程模型与同步
在共享内存系统中编程,主要使用线程。
- 通信:通过共享变量进行通信。线程间通过读写共享的内存地址来传递信息。
- 同步:为了防止多个线程同时修改同一数据导致竞态条件,必须使用同步原语来协调线程的执行顺序。
- 锁:最常用的机制。保证只有获得锁的线程才能进入临界区访问共享资源。
- 信号量:用于控制访问资源的线程数量。
- 条件变量:用于线程间等待和通知特定条件的发生。
5. 优势与劣势
优势:
- 编程模型直观:与单机编程模型相似,易于理解和学习。通信简单(直接读写内存)。
- 数据共享高效:不需要在内存之间显式地移动数据,共享大量数据时优势明显。
- 负载均衡:由于所有处理器地位平等,任务可以动态地分配给任何空闲的处理器。
劣势:
- 可扩展性限制:虽然NUMA改善了这一点,但内存带宽和互联网络最终仍会成为瓶颈,难以扩展到成千上万个处理器。
- 同步开销大:大量的同步操作(如锁竞争)会成为性能瓶颈。
- 对程序员要求高:容易引入难以调试的并发Bug,如死锁、竞态条件。
总结
共享内存多处理器通过提供一个统一的全局内存视图,提供了直观且高效的并行编程环境。它是现代多核CPU和主流服务器的基石。其核心挑战——缓存一致性和内存一致性——已通过复杂的硬件协议(MESI)和明确的内存模型(如x86-TSO)得到解决。尽管在极端可扩展性上不如分布式内存系统,但它无疑是在性能、成本和编程便利性之间取得的最佳平衡点。