如果系统是缓存一致的,那么您是否可以在固定到不同处理器的两个线程之间进行读取/写入



如果在同一处理器中有两个线程,那么读/写可能会被破坏。

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

  1. 线程1将64位int 0xffffffffff赋值给全局变量X,该变量最初为零
  2. 前32位设置为前32位在X中设置,现在X为0xffffff00000000
  3. 线程2将X读取为0xffffff00000000
  4. 线程1写入最后32位

在步骤3中发生读取损坏。

但是如果满足以下条件怎么办:

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

在这种情况下,撕裂读取是否仍然可能?或者在步骤3中,高速缓存行会被视为无效,从而防止读取被破坏?

是的,你可能会撕裂。

对该行的共享请求可能介于提交两个独立的32位存储之间。如果它们是由单独的指令完成的,那么写线程甚至可能在第一个和第二个存储之间中断,从而击败任何合并在存储缓冲区中的存储(像一些32位RISC CPU记录的那样,合并为对齐的64位提交(,这通常会使在实践中很难观察到单独的32位存储之间的撕裂。

另一种导致撕裂的方法是,如果读取端在读取前半部分之后,在读取后半部分之前,失去了对缓存线的访问。(因为它收到了来自写入程序核心的RFO(读取所有权(。(第一次读取可以看到旧值,第二次读取可以查看新值。

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

(如果互连本身没有引入撕裂,请注意AMD K10 Opteron的例子,它撕裂了单独插槽中内核之间的8字节边界,但似乎在同一插槽中的内核之间对齐了16字节原子性。x86手册只保证8字节原子性,因此16字节原子度超出了文档中的保证范围,这是实现的副作用。(

当然,一些32位ISAs有一个加载对或存储对指令,或者(像x86一样(通过FPU/SIMD单元保证64位对齐加载/存储的原子性。


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

当一行正在执行一对加载或一对存储时,通过延迟对MESI请求的响应来共享或使其无效,而这对加载或存储是用一条特殊的指令完成的,当正常的加载-对或存储-对不会执行时,该指令会给出原子性。另一个核心一直在等待响应,因此必须严格限制延迟响应的时间,否则就会出现饥饿/整体吞吐量低的问题。

通常对加载对/存储对的缓存进行64位访问的微体系结构将通过将一次缓存访问拆分为两个寄存器输出来免费获得原子性。

但是低端实现可能没有这么宽的缓存访问硬件。也许只有LL/SC特殊指令具有2寄存器原子性。(IIRC,ARM的某些版本就是这样。(

进一步阅读:

  • x86上的原子性-单个加载或存储是如何成为原子的
  • 为什么在x86上,自然对齐变量上的整数赋值是原子的
  • num++对于';int num';?-原子RMW如何与MESI相互作用。(对于像lock add [mem], eax.LL/SC这样的x86风格的单指令,只要检测到它们在某个地方失去了对缓存行的控制并报告失败。(

相关内容

最新更新