1

鉴于x86总存储顺序和Java存储器模型中发生的事前关系,我们知道编译器不保证指令执行的顺序。它可以根据需要重新排序,以提高性能。鉴于此,我们有:Java内存模型和并发

  • EAXEBX是寄存器
  • [x][y]是内存位置
  • r1r2是局部变量的名称
  • xy共享变量访问的名字所有线程。所有变量都是32位整数。
  • 不,这是不是一个家庭问题

所以我有两套的问题,我想,以确定可能的输出:

[x] == [y] == 0 // the address space of [x] and [y] are 0. 

// Thread 1       Thread 2 
MOV [x] <- 1      MOV [y] <- 1 
MOV EAX <- [y]      MOV EBX <- [x] 

这对寄存器EBXEAX的可能值?

int x = 0; 
int y = 0; 

// Thread 1       Thread 2 
x = 1;        y = 1; 
r1 = y;        r2 = x; 

什么r1r2的可能值?

+1

IMO,您应该将x86架构与Java分离。这是一个关于JVM被允许做什么的问题,或者这是一个关于x86处理器实际上可以做什么的问题。至于JVM被允许做什么,线程1可以将0或1分配给'r1',而线程2可以将0或1分配给r2。 –

回答

4

32-bit integer保证是atomicJVM,所以这不是一个问题。

您有2个变量x和y在线程之间共享,没有synchronization

  1. Thread1突变x并读取y。
  2. Thread2突变y并读取x。

因此,thread1可以看到y的陈旧值(1或0),并且thread2可以看到陈旧值x(1,0)。

这意味着你可以得到的(EAX,EBX)所有四种可能的组合: (0,0) (0,1) (1,0) (1,1)

+1

你的答案的内容是好的,但我觉得对普通单词使用'代码格式化'是不必要的... – Nayuki

+0

@NayukiMinase我不是一个SO专家,我使用“代码格式化”突出重要单词,随意编辑:) –

+1

不是像你的那样得分的专家吗?确实很谦虚。无论如何,既然你回答了,我会把编辑留给你的判断。 – Nayuki

2

我们可以简单的标签说明如下:

A) [x] <- 1   C) [y] <- 1 

B) EAX <- [y]   D) EBX <- [x] 

我们知道,A B之前到来,和C自带d之前,所以只需插入C和d为AB在所有可能的排列:

CDAB 
CADB 
CABD 
ACDB 
ACBD 
ABCD 

,并考虑各种可能性的影响,并指出,多数开始要么ACCA,输出(EAX,EBX)=(1,1)因为分配是EAX和之前发生正在设置。剩下的就是检查其他两种可能性。 CDAB给出(EAX,EBX)=(1,0)ABCD给出(EAX,EBX)=(0,1)

对于Java版本,您声明编译器不保证执行语句的顺序。在这种情况下,订购A,B,CD以获得(0,0),(1,0),(0,1)和(1,1)并不困难。

+0

第一个问题呢? – cybertextron

+0

@philippe第一个问题是刚才用汇编写的同样的问题。问题编写者我假设用r1表示“寄存器1”或EAX,而r2表示“寄存器2”或EBX。 –

+0

我们知道A在B之前吗?我不认为Java提供这种保证。从线程1的角度来看,A和B是独立的,在这种情况下,重新排序指令是可行的。 – zapl

4

86有一个强烈排序的内存模型,但仍然允许StoreLoad reordering

Jeff Preshing的博客文章:Memory Reordering Caught in the Act,恰好使用那对商店 - 然后加载序列作为测试用例来证明重新排序确实可以在真实硬件上观察到。他有源代码和一切。

请注意,每个线程都有自己的架构状态(包括所有的寄存器)。所以thread1的EAX与thread2的EAX不同。在thread2中使用EBX使得它更容易讨论,与发生什么事情的POV没什么不同。

无论如何,两个寄存器都可以以0结束。这很少发生,但它可以,因为每个线程的存储可以被延迟(在存储缓冲区或其他任何地方),直到其他线程的加载选择了一个值之后。这是合法的,CPU可以积极地使用预取的数据来满足负载,并缓冲存储,以便它们在退休时不会立即全局可见。 (“退休”是指运行指令的线程的体系结构状态(包括EIP)已转移到下一条指令,并且提交了效果。)

其他可能性,一旦尘埃落定,总是包括全局变量是1。在每个线程的寄存器中,所有4个可能的0和1值都是可能的,包括1。他们有可能看到彼此的商店。我不确定这有多可能;它可能需要一个线程在其存储之后但在其加载之前被中断。如果两个线程都在同一个物理内核上运行(超线程),则为this possibility is much more likely


即使对于xy存储是不对齐和跨越高速缓存线,和01是唯一可能的值。 (C编译器输出和JVMs会将变量与它们的自然对齐对齐,这使得它不成问题,但是你可以在asm中做任何你想做的事情,所以我想我会提到它)。这是因为这两个值只有不同在最低有效字节中。

如果你存储一个32位的跨越两个高速缓存行-1为4个字节,其它线程可能(取决于高速缓存行边界在何处)加载的0x00ffffff0xff0000000x0000ffff0xffff0000等的值,以及通常的00xffffffff(又名-1)。


回复:Java。我没有阅读Java内存模型。其他答案是说它甚至允许编译时重新排序(如c++11's std::atomic rules)。即使不是,没有完整的内存屏障,StoreLoad重新排序也会发生。所以所有四个结果都是可能的

即使您的JVM运行在x86 CPU上(而不是像ARM这样微弱有序的硬件),情况也是如此。

This answer to another question可能会说明为什么在x86上存在LFENCE/SFENCE,尽管它们在大多数情况下都是无操作的。 (即,当不使用movnt或弱排序存储区域(如USWC视频存储器))。

或者,您可以阅读Jeff Preshing的其他博客文章,以了解更多关于内存订购的信息。我发现它真的帮助我自己。