11

在 Java 中,当我们有两个线程共享以下变量时:

int a;
volatile int b;

如果线程 1 这样做:

a = 5;
b = 6;

然后在这两条指令之间插入一个 StoreStore 屏障,并将“a”刷新回主存储器。

现在如果线程 2 这样做:

if(b == 6)
 a++;

之间插入了一个 LoadLoad 屏障,我们保证如果“b”的新值是可见的,那么“a”的新值也是可见的。但实际上这是如何实现的呢?LoadLoad 是否会使 CPU 缓存/寄存器无效?或者只是指示 CPU 从 CPU 再次从 volatile 中获取从 volatile 读取的变量的值?

我找到了有关 LoadLoad 屏障的信息(http://gee.cs.oswego.edu/dl/jmm/cookbook.html):

LoadLoad Barriers 顺序:Load1;加载加载;Load2 确保在 Load2 访问数据之前加载 Load1 的数据,并加载所有后续加载指令。通常,在执行推测加载和/或等待加载指令可以绕过等待存储的无序处理的处理器上需要显式 LoadLoad 屏障。在保证始终保持加载顺序的处理器上,障碍相当于无操作。

但它并没有真正解释这是如何实现的。

4

2 回答 2

4

我将举一个例子来说明这是如何实现的。您可以在此处阅读有关详细信息的更多信息。对于您指出的 x86 处理器,LoadLoad 最终成为无操作。在我链接的文章中,马克指出

Doug 列出了 StoreStore、LoadLoad 和 LoadStore

所以本质上,唯一需要的障碍是 x86 架构的 StoreLoad。那么这是如何在低水平上实现的呢?

这是博客的摘录:

这是它为易失性和非易失性读取生成的代码:

nop                       ;*synchronization entry
mov    0x10(%rsi),%rax    ;*getfield x

对于易失性写入:

xchg   %ax,%ax
movq   $0xab,0x10(%rbx)
lock addl $0x0,(%rsp)     ;*putfield x

lock指令是 Doug 的食谱中列出的 StoreLoad。但是锁定指令还会将所有读取与列出的其他进程同步

锁定指令可用于同步一个处理器写入和另一个处理器读取的数据。

这减少了必须为易失性负载发出 LoadLoad LoadStore 屏障的开销。

说了这么多,我将重申 assylias 指出的内容。它发生的方式对开发人员来说不应该是重要的(如果您对处理器/编译器实现者感兴趣,那就是另一回事了)。volatile关键字是一种接口说

  1. 您将获得由另一个线程编写的最新阅读
  2. 您不会被 JIT 编译器优化所困扰。
于 2013-03-12T12:46:39.627 回答
0

如果该 LoadLoad 评估为无操作,则线程 2 可以继续使用缓存值。

食谱中的“可以订购”表涵盖了这一点。

编程顺序是

read b
read a
write a

通过“缓存a”,您的意思是代码被重新排序

read a
...
read b

禁止重新排序。

于 2013-03-12T15:40:44.413 回答