如果系统缓存一致,您能否在固定到不同处理器的两个线程之间撕裂 reads/writes?

Can you have torn reads/writes between two threads pinned to different processors, if the system is cache coherent?

如果您在同一个处理器中有两个线程,则可以撕裂 read/write。

例如,在线程 1 和线程 2 运行 位于同一内核的 32 位系统上:

  1. 线程 1 将一个 64 位 int 0xffffffffffffffff 分配给一个初始为零的全局变量 X。
  2. 前32位设置为X设置前32位,现在X为0xffffffff00000000
  3. 线程 2 将 X 读取为 0xffffffff00000000
  4. 线程 1 写入最后 32 位。

撕裂读取发生在第 3 步。

但是如果满足以下条件呢:

  1. 线程 1 和线程 2 固定到不同的内核
  2. 系统使用MESI协议实现缓存一致性

这样的话,撕裂读还有可能吗?或者缓存行是否会在步骤 3 中被视为无效,从而防止撕裂读取?

是的,你可以撕裂。

该行的 share-request 可能介于提交两个单独的 32 位存储之间。如果它们是通过单独的指令完成的,写入线程甚至可以在第一个和第二个存储之间进行中断,从而阻止存储缓冲区 () 中的任何存储合并,这通常会导致很难观察到撕裂在单独的 32 位存储之间练习。

另一种导致撕裂的方法是,如果读取端在读取前半部分之后失去对缓存行的访问权限,然后再读取第二部分。 (因为它从写入器核心接收到 RFO(读取所有权)。)第一次读取可以看到旧值,第二次读取可以看到新值。

唯一安全的方法是存储和加载都作为对各自核心的 L1d 缓存的单个原子访问来完成。

(如果互连本身不引入撕裂;请注意 AMD K10 Opteron 的情况 that tears on 8-byte boundaries between cores on separate sockets,但似乎在同一插槽中的内核之间具有对齐的 16 字节原子性。仅限 x86 手册保证 8 字节的原子性,因此 16 字节的原子性作为实现的副作用超出了记录的保证。)

当然,某些 32 位 ISA 具有 load-pair 或 store-pair 指令,或者(如 x86)保证通过 FPU 完成的 64 位对齐 loads/stores 的原子性 / SIMD 单元。


如果撕裂通常是可能的,那么这样的微体系结构将如何实现 64 位原子操作?

通过延迟对 MESI 请求的响应以共享或使行无效时,当它正在执行一对加载或一对存储时使用特殊指令完成,该指令在正常 load-pair 或 store-pair 不会。另一个核心卡住等待响应,因此必须严格限制您可以延迟响应多长时间,否则饥饿/整体吞吐量进度低是一个问题。

通常对 load-pair / store-pair 缓存进行 64 位访问的微体系结构将通过将一个缓存访问分成两个寄存器输出来免费获得原子性。

但是 low-end 实现可能没有这么宽的 cache-access 硬件。也许只有 LL/SC 个特殊指令具有 2 个寄存器的原子性。 (IIRC,某些版本的 ARM 就是这样。)

进一步阅读:

  • - 单个加载或存储究竟有多原子化
  • - 原子 RMW 如何与 MESI 交互。 (对于像 lock add [mem], eax.LL/SC 这样的 x86 风格的单指令,机器只是 检测到 它们在某处失去了对缓存行的控制并报告失败。)