另一周,我写了一个小线程类和一个单向消息管道,以允许线程之间的通信(每个线程两个管道,显然,用于双向通信)。在我的Athlon 64 X2上一切正常,但我想知道如果两个线程都在查看相同的变量并且每个核心上的此变量的本地缓存值不同步,我是否会遇到任何问题。
我知道 volatile 关键字会强制变量从内存中刷新,但多核x86处理器是否有办法强制所有内核的缓存同步?这是我需要担心的事情,还是 volatile 并且正确使用轻量级锁定机制(我使用_InterlockedExchange来设置我的易失性管道变量)处理所有我想写“无锁”的情况多核x86 CPU的代码?
我已经知道并使用了Critical Sections,Mutexes,Events等。我主要想知道是否有x86内在函数,我不知道哪种力量可以用来强制缓存一致性。
答案 0 :(得分:31)
volatile
仅强制您的代码重新读取该值,它无法控制从中读取值的位置。如果你的代码最近读取了这个值,那么它可能会在缓存中,在这种情况下,volatile会强制它从缓存中重新读取,而不是从内存中读取。
x86中没有很多缓存一致性指令。有一些预取指令,如prefetchnta
,但这不会影响内存排序语义。它过去是通过将值带到L1缓存而不会污染L2来实现的,但对于具有大型共享包含 L3缓存的现代英特尔设计而言,情况会更复杂。
x86 CPU使用MESI protocol(英特尔的MESIF,AMD的MOESI)的变体来保持其缓存彼此一致(包括不同核心的私有L1缓存)。想要编写缓存行的核心必须强制其他核心使其副本无效,然后才能将自己的副本从“共享”更改为“已修改”状态。
您不需要任何围栏指令(如MFENCE)在一个线程中生成数据并在x86上使用另一个线程,因为x86加载/存储内置acquire/release semantics。您确实需要MFENCE(全屏障)以获得顺序一致性。 (此答案的先前版本表明需要clflush
,这是不正确的。)
你需要阻止compile-time reordering,因为C ++的内存模型是弱排序的。 volatile
这是一个古老而糟糕的方式; C ++ 11 std :: atomic是一种更好的编写无锁代码的方法。
答案 1 :(得分:24)
由于x86处理器采用MESI协议,因此可以保证内核之间的高速缓存一致性。在处理外部硬件时,您只需要担心内存一致性,外部硬件可能会在数据仍位于内核缓存上时访问内存。但是,这看起来不像你的情况,因为文本建议你在用户区编程。
答案 2 :(得分:14)
您无需担心缓存一致性。硬件将负责这一点。您可能需要担心的是由于缓存一致性导致的性能问题。
如果核心#1写入变量而核心#2读取相同的变量,则处理器将确保更新核心#2的缓存。由于必须从内存中读取整个高速缓存行(64字节),因此会产生一些性能成本。在这种情况下,这是不可避免的。这是理想的行为。
问题在于,当同一缓存行中有多个变量时,即使内核在同一缓存行中读取/写入不同的变量,处理器也可能会花费额外的时间来保持缓存同步。通过确保这些变量不在同一缓存行中,可以避免该成本。此效果称为 False Sharing ,因为您强制处理器同步线程之间实际不共享的对象的值。
答案 3 :(得分:6)
挥发性不会这样做。在C ++中,volatile仅影响编译器优化,例如将变量存储在寄存器而不是内存中,或者完全删除它。
答案 4 :(得分:6)
您没有指定使用的编译器,但如果您使用的是Windows,请查看this article here。另请查看可用的s ynchronization functions here。您可能需要注意的是,通常volatile
不足以完成您希望它执行的操作,但在VC 2005和2008中,添加了非标准语义,这会在读取和写入之间添加隐含的内存屏障
如果你想让事情变得便携,你将面临更艰难的道路。
答案 5 :(得分:3)
有一系列文章解释了现代内存架构 here,包括Intel Core2 caches以及更多现代架构主题。
文章非常易读且图文并茂。享受!
答案 6 :(得分:3)
您的问题中有几个子问题,所以我会尽我所能回答它们。
答案 7 :(得分:2)
以下是关于使用volatile
w / thread程序的好文章。
答案 8 :(得分:1)
Herb Sutter似乎只是suggest,任何两个变量都应该驻留在不同的缓存行上。他在他的并发队列中执行此操作,并在其锁和节点指针之间填充。
编辑:如果你正在使用英特尔编译器或GCC,你可以使用atomic builtins,它们似乎尽可能地抢占缓存。