我们假设变量a = 0
Processor1: a = 1
Processor2: print(a)
Processor1首先执行它的指令然后在下一个周期处理器2读取变量以打印它。所以是:
P1: |--a=1--|---cache--coherence---|----------------
P2: ------|stalls due to coherence-|--print(a=1)---|
time: ----------------------------------------------->
P1: |--a=1--|---cache--coherence---|
P2: ----------|---print(a=0)---|----
time: ------------------------------->
换句话说,在完成高速缓存一致性操作之前,处理器是否具有过时的内存视图?所有现代ISA都使用(变体)MESI来缓存一致性。这在所有处理器具有的内存(通过高速缓存)的共享视图的所有时间保持一致性。
例如,参见Can I force cache coherency on a multicore x86 CPU?这是一个常见的误解,即存储进入缓存,而其他核心仍然具有缓存行的旧副本,然后必须发生“缓存一致性”。
但事实并非如此:要修改缓存行,CPU需要拥有该行的独占所有权(MESI的修改或独占状态)。只有在收到对“读取所有权”的响应后,才能实现此操作,该响应使高速缓存行的所有其他副本无效,如果之前处于“共享”或“无效”状态。例如,请参阅Will two atomic writes to different locations in different threads always be seen in the same order by other threads?。
但是,内存模型允许对商店和负载进行本地重新排序。顺序一致性太慢,因此CPU始终至少允许StoreLoad重新排序。有关x86上使用的TSO(总存储顺序)内存模型的大量详细信息,另请参阅Is mov + mfence safe on NUMA?。许多其他ISA使用更弱的模型。
对于这种情况下的非同步读取器,如果两者都在不同的核上运行,则有三种可能性
load(a)
在缓存行无效之前发生在核心#2上,因此它读取旧值,因此在a=1
存储在全局顺序之前有效地发生。负载可以在L1d缓存中命中。load(a)
发生在核心#1将商店提交到其L1d缓存之后,还没有回写。 Core#2的读取请求触发Core#2回写共享共享级别的缓存(例如L3),并将该行置于共享状态。在L1d中肯定会错过负载。load(a)
发生在回写到内存之后或者至少L3已经发生,所以它不必等待核心#1回写。除非硬件预取由于某种原因使其重新进入,否则负载将在L1d中丢失。但通常只会作为顺序访问的一部分(例如,对阵列)发生。所以是的,如果另一个核心已经将其提交到缓存,则在此核心尝试加载之前,加载将停止。
另请参阅Size of store buffers on Intel hardware? What exactly is a store buffer?以获取有关存储缓冲区对所有内容(包括内存重新排序)的影响的更多信息。
这没关系,因为你有一个只写生产者和一个只读消费者。生产者核心不会等待它的商店在继续之前变得全局可见,并且它可以在它变得全局可见之前立即看到它自己的商店。当你让每个线程看到由另一个线程完成的商店时,这很重要;然后你需要障碍,或顺序一致的原子操作(编译器实现障碍)。见https://preshing.com/20120515/memory-reordering-caught-in-the-act
另请参阅Can num++ be atomic for 'int num'?,了解RMW如何与MESI一起使用,这对于理解这个概念是有益的。 (例如,原子RMW可以通过将核心挂在处于已修改状态的高速缓存行上来工作,并延迟响应RFO或请求共享它,直到RMW的写入部分已提交。)
在此示例中对a
的读写访问是并发的,它们可以按任何顺序完成。这取决于首先访问该行的处理器。高速缓存一致性仅保证同一相干域中的所有处理器都同意存储在所有高速缓存行中的值。所以最终结果不能是有两个a
副本,一个值为0,另一个值为1。
如果要确保处理器2看到processor1写入的值,则必须使用同步机制。实现这一目标的一种简单但低效的方法是:
Processor1:
a = 1
rel = 1
Processor2:
while(rel != 1){ }
print(a)
如果满足以下属性,则此方法有效:
满足这些属性的ISA的示例是x86-64,假设rel
不大于8个字节并且自然对齐并且所有变量不从WC存储器类型的存储器区域分配。
关于您对问题的更新。如果处理器1在处理器2读取之前已获得该线路的所有权,则处理器2可以停止,直到处理器1完成其写入操作并获得更新的线路。如果线路检测到来自另一个处理器的线路的读取请求,则处理器1可以在写入线路之前决定放弃该线路的所有权。但无论发生什么,这两个访问将按某种顺序完成。