乱序执行和重新排序:我可以看到屏障之前的屏障之后的内容吗?

Out-of-order execution and reordering: can I see what after barrier before the barrier?

根据维基百科:内存屏障,也称为 membar、内存栅栏或栅栏指令,是一种导致中央处理器 (CPU) 或编译器强制执行排序的屏障指令对屏障指令前后发出的内存操作的约束。 这通常意味着在屏障之前发出的操作保证在屏障之后发出的操作之前执行。

通常,文章谈论类似(我将使用监视器而不是 membars):

class ReadWriteExample {                                          
    int A = 0;    
    int Another = 0;                                                

    //thread1 runs this method                                    
    void writer () {                                              
      lock monitor1;   //a new value will be stored            
      A = 10;          //stores 10 to memory location A        
      unlock monitor1; //a new value is ready for reader to read
      Another = 20; //@see my question  
    }                                                             

    //thread2 runs this method                                    
    void reader () {                                              
      lock monitor1;  //a new value will be read               
      assert A == 10; //loads from memory location A
      print Another //@see my question           
      unlock monitor1;//a new value was just read              
   }                                                              
}       

但我想知道编译器或 cpu 是否有可能以代码将打印 20 的方式改变周围的事物?我不需要保证。

即根据定义,在屏障之前发出的操作不能被编译器下推,但是在屏障之后发出的操作是否有可能偶尔会在屏障之前出现? (只是一个概率)

谢谢

我下面的回答只涉及 Java 的内存模型。确实无法为所有语言做出答案,因为每种语言可能定义不同的规则。

But I wonder is it possible that compiler or cpu will shuffle the things around in a such way that code will print 20? I don't need guarantee.

你的答案似乎是"Is it possible for the store of A = 20, be re-ordered above the unlock monitor?"

答案是,可以。如果您查看 JSR 166 Cookbook,显示的第一个网格解释了重新排序的工作原理。

在您的 writer 情况下,第一个操作将是 MonitorExit,第二个操作将是 NormalStore。网格解释,是的,这个序列允许重新排序。

这就是所谓的Roach Motel顺序,即内存访问可以移入同步块但不能移出


换一种语言呢?好吧,这个问题太宽泛了,无法回答所有问题,因为每个问题对规则的定义可能不同。如果是这种情况,您需要完善您的问题。

在Java中有happens-before的概念。您可以在 Java Specification 中阅读有关它的所有详细信息。 Java 编译器或运行时引擎可以重新排序代码,但它必须遵守 happens-before 规则。这些规则对于 Java 想要详细控制代码重新排序方式的开发人员来说很重要。我自己也被重新排序代码搞得焦头烂额,结果我通过两个不同的变量引用了同一个对象,运行时引擎重新排序了我的代码,却没有意识到这些操作是在同一个对象上进行的。如果我有一个 happens-before(在两个操作之间)或使用相同的变量,那么就不会发生重新排序。

具体来说:

It follows from the above definitions that:

An unlock on a monitor happens-before every subsequent lock on that monitor.

A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.

A call to start() on a thread happens-before any actions in the started thread.

All actions in a thread happen-before any other thread successfully returns from a join() on that thread.

The default initialization of any object happens-before any other actions (other than default-writes) of a program.

简短回答 - 是的。这非常依赖于编译器和 CPU 体系结构。你在这里有一个竞争条件的定义。调度 Quantum 不会在指令中途结束(不能对同一位置进行两次写入)。但是 - 量程可能会在指令之间结束 - 加上它们在管道中的乱序执行方式取决于体系结构(在监视器块之外)。

"it depends" 并发症来了。 CPU 保证很少(参见竞争条件)。您还可以查看 NUMA (ccNUMA) - 它是一种通过将 CPUs(节点)与本地 RAM 和组所有者分组来扩展 CPU 和内存访问的方法 - 以及节点之间的特殊总线。

监视器不会阻止其他线程 运行ning。它只会阻止它在监视器之间输入代码。因此,当 Writer 退出 monitor-section 时,它可以自由执行下一条语句——无论其他线程是否在 monitor 中。监视器是阻止访问的门。此外 - 量程可以在 A== 语句之后中断第二个线程 - 允许 Another 更改值。再次 - 量子不会中断中间指令。始终考虑以完美并行方式执行的线程。

你如何应用它?我对当前的英特尔处理器以及它们的流水线如何工作(超线程等)有点过时(抱歉,现在是 C#/Java)。多年前,我使用了一个名为 MIPS 的处理器——它(通过编译器指令排序)能够执行在分支指令(延迟槽)之后连续发生的指令。在这个 CPU/Compiler 组合上 - 是的 - 你所描述的可能会发生。如果英特尔提供相同的——那么是的——它可能会发生。 Esp 和 NUMA(Intel 和 AMD 都有,我最熟悉 AMD 的实现)。

我的观点 - 如果线程 运行 跨 NUMA 节点 - 并且访问是对公共内存位置,那么它可能会发生。当然 OS 努力在同一节点内安排操作。

你或许可以模拟这个。我知道 MS 上的 C++ 允许访问 NUMA 技术(我玩过它)。查看是否可以跨两个节点分配内存(将 A 放在一个节点上,将另一个节点放在另一个节点上)。将线程安排到特定节点上的 运行。

在这个模型中发生的事情是有两条通往 RAM 的路径。我想这不是您想要的 - 可能只是一个 path/Node 模型。在这种情况下,我回到上面描述的 MIPS 模型。

我假定处理器会中断 - 还有其他处理器具有 Yield 模型。