是否有可能从另一个线程写入的内存中读取一个值,这样它既不是原始值也不是最终值?

时间:2017-08-25 12:12:48

标签: multithreading x86

假设我们在内存中有一个变量,它通过一个执行线程不断更新,通过交替值对MOV进行操作(单个更新由单个指令完成)。是否有可能另一个线程,读取这个相同的变量,将既没有得到替代值,也没有得到不同的东西 - 可能是一半,半替代或其他什么?

如果有可能,我该如何重现这种情况?可以通过错位或变量的其他特殊位置帮助实现这一目标吗?

如果在现代CPU上不可能,那么对于一些较旧的CPU是否可能?

1 个答案:

答案 0 :(得分:3)

将变量拆分为缓存行边界。然后加载和存储都不会是原子的,并且你会在所有真实的CPU上练习撕裂。

e.g。在NASM语法中:

section .bss
align 64
       resb 63   ; reserve 63 bytes
myvar: resd 1    ; reserve 1 dword (32 bits)

要制作一个在实践中证明这一点的测试程序,请参阅SSE instructions: which CPUs can do atomic 16B memory operations?以获取示例。

此外,80位x87 long double在某些硬件上是非原子的。 80位x87 fld / fstp解码到2个单独的加载或存储uops(加上一些ALU uop)(例如on Intel Sandybridge-family),所以可能是64位部分和16 -bit部分是单独的高速缓存访​​问,即使在16字节SSE long double是原子的CPU上,你也可能会对任何对齐的movaps [mem], xmm0进行撕裂。

没有英特尔或AMD x86手册可以保证任何宽度超过64位的原子性(lock cmpxchg16b除外),所以这个关于SSE向量加载/存储在某些CPU上是原子的说法不是你可以可靠的利用或检测何时支持它。 (虽然在某些硬件上(比如Intel Haswell / Skylake,至少是单插槽),如果它们不跨越缓存行边界,即使是32字节的YMM加载/存储也是原子的。)

有关规则,请参阅Why is integer assignment on a naturally aligned variable atomic?。违反其中任何一个,你可以看到某些 CPU上的撕裂。

保证所有 SMP系统的非原子性,越过64B边界将始终有效(从技术上讲,你应检查CPUID以找出缓存行大小,如果它更大,但自从最后的32B缓存线系统(Pentium III)以来64B已成为标准。)

超级保证绝对可以一直工作(除了CPU设计与当前的设计根本不同):拆分1GiB边界,因为这是最大的页面大小。 (甚至4k在2MB的巨页数内分割为页面拆分,需要两次TLB检查才能发现它们都在同一个巨页中,并且当前硬件的相关性能受到惩罚。当然,任何4k拆分也是一个缓存-line split)。

所有这一切的一个例外是单处理器机器,因为上下文切换不能在指令的中间发生。 mov存储或加载要么在中断之前发生,要么不发生。 (对于其他线程,单处理器甚至对add [mem], 1原子进行读取 - 修改 - 写入,尽管它与DMA或MMIO观察者无关。请参阅supercat在Can num++ be atomic for 'int num'?上的答案