Ign*_*ant 5 multithreading synchronization cpu-architecture low-level memory-barriers
考虑以下取自维基百科的示例,稍作修改,其中程序的步骤对应于各个处理器指令:
x = 0;
f = 0;
Thread #1:
while (f == 0);
print x;
Thread #2:
x = 42;
f = 1;
Run Code Online (Sandbox Code Playgroud)
我知道,print由于乱序执行,当线程在两个不同的物理内核/处理器上运行时,该语句可能会打印不同的值(42 或 0)。
但是我不明白为什么这在单核机器上不是问题,这两个线程运行在同一个核心上(通过抢占)。根据维基百科:
当程序在单 CPU 机器上运行时,硬件执行必要的簿记以确保程序执行时好像所有内存操作都按照程序员指定的顺序(程序顺序)执行,因此不需要内存屏障。
据我所知,单核 CPU 也会对内存访问重新排序(如果它们的内存模型很弱),那么是什么确保程序顺序被保留呢?
CPU 不会意识到这是两个线程。线程是一种软件结构 (1)。
所以 CPU 看到这些指令,按以下顺序:
store x = 42
store f = 1
test f == 0
jump if true ; not taken
load x
Run Code Online (Sandbox Code Playgroud)
如果 CPU 将 x 的存储重新排序到最后,在加载后,它会改变结果。虽然允许 CPU 乱序执行,但它仅在不改变结果时才这样做。如果允许这样做,几乎所有指令序列都可能失败。不可能产生一个工作程序。
在这种情况下,不允许单个 CPU 重新排序存储超过相同地址的负载。至少,就 CPU 而言,它没有重新排序。就 L1、L2、L3 缓存和主内存(以及其他 CPU!)而言,可能存储尚未提交。
(1) 像 HyperThreads 这样的东西,每个内核有两个线程,在现代 CPU 中很常见,在您的问题中不算作“单 CPU”。
CPU 不知道也不关心“上下文切换”或软件线程。它所看到的只是一些存储和加载指令。(例如,在操作系统的上下文切换代码中,它保存旧寄存器状态并加载新寄存器状态)
乱序执行的基本规则是它不能破坏单个指令流。 代码必须像按程序顺序执行的每条指令一样运行,并且在下一条指令开始之前它的所有副作用都已完成。这包括单核上线程之间的软件上下文切换。例如单核机器或进程内的绿色线程。
(通常我们会将此规则声明为不破坏单线程代码,并理解这究竟意味着什么;只有当 SMP 系统从其他内核存储的内存位置加载时才会发生奇怪的事情)。
据我所知,单核 CPU 也会重新排序内存访问(如果它们的内存模型很弱)
但请记住,其他线程不会直接使用逻辑分析器(甚至 DMA)观察内存,它们只是在执行和跟踪重新排序的同一 CPU 内核上运行加载指令。
如果您正在编写设备驱动程序,是的,您可能必须在存储之后实际使用内存屏障,以确保在从另一个 MMIO 位置加载之前,它实际上对片外硬件可见。
或者在与 DMA 交互时,确保数据实际上在内存中,而不是在 CPU 私有的回写缓存中可能是一个问题。(现代 x86 具有缓存一致 DMA,因此您不必实际刷新回 DRAM,只需确保其全局可见,例如使用 x86 之类的指令mfence等待存储缓冲区耗尽。)此外,MMIO 通常在不可缓存中完成暗示强内存排序的内存区域。
顺便说一句,即使是 x86 的“强”内存模型也只是 acq/rel,而不是 seq_cst(除了完全屏障的 RMW 操作)。(或者更具体地说,在顺序一致性之上具有存储转发的存储缓冲区)。存储可以延迟到稍后加载之后。(StoreLoad 重新排序)。见https://preshing.com/20120930/weak-vs-strong-memory-models/
那么是什么确保程序顺序被保留?
硬件依赖跟踪;加载监听存储缓冲区以从最近存储到的位置查找加载。这确保加载将数据从最后一个程序顺序写入到任何给定的内存位置1。
没有这个,代码就像
x = 1;
int tmp = x;
Run Code Online (Sandbox Code Playgroud)
可能会加载x. 如果您必须在每次存储后为自己的重新加载设置内存屏障以可靠地查看存储的值,那将是疯狂且无法使用的(并且会降低性能)。
根据 ISA 规则,我们需要在单个内核上运行所有指令,以产生按程序顺序运行的错觉。只有 DMA 或其他 CPU 内核可以观察到重新排序。
脚注 1:如果旧存储的地址尚不可用,CPU 甚至可能会推测它将到不同的地址并从缓存加载,而不是等待存储指令的存储数据部分执行。如果它猜错了,它将不得不回滚到已知的良好状态,就像分支预测错误一样。这称为“记忆消歧”。另请参阅x86 处理器中的Store-to-Load Forwarding 和 Memory Disambiguation 以进行技术研究,包括从更广泛存储的一部分进行狭窄重新加载的情况,包括未对齐和可能跨越缓存线边界...
| 归档时间: |
|
| 查看次数: |
441 次 |
| 最近记录: |