【问题标题】:What does a LoadLoad barrier really do?LoadLoad 屏障的真正作用是什么?
【发布时间】:2013-02-27 22:17:38
【问题描述】:

在 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 之后的变量值?

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

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

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

【问题讨论】:

  • 答案取决于处理器架构 - 同一份文档有一个表格,其中包含每个处理器指令,例如,LoadLoad 是 x86 上的无操作。
  • 那么它到底是如何工作的呢?我的意思是,在 StoreStore 之后,值会被刷新回内存中。但是线程 2 应该如何看到它们呢?如果 LoadLoad 计算结果为无操作,则线程 2 可以继续使用缓存值。
  • 因为处理器的内存模型足够强大,可以保证它会是这种情况。我想说的是,Java 承诺如果您使用 volatile,某些事情将会/不会发生。这是如何在 JVM 中实现的,是特定于处理器的,并使用临时指令(如果相关,也可以不使用指令)。您可以在此处阅读有关 LoadLoad/x86 点的更多信息:altair.cs.oswego.edu/pipermail/concurrency-interest/2012-July/…
  • 您可以进一步了解 LoadLoad 不是无操作的 CPU 架构,例如 ARM。不过似乎很重的东西:)

标签: java concurrency volatile


【解决方案1】:

我将举一个例子来说明这是如何实现的。您可以阅读更多详情here。对于您指出的 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。但是锁定指令也会将所有读取与其他进程同步为listed

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

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

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

  1. 您将获得由另一个线程编写的最新阅读内容
  2. 您不会被 JIT 编译器优化所困扰。

【讨论】:

  • 不错的链接。我会稍微改写一下:“您将获得由另一个线程写入的最新读取数据”=>“您最终将在随后从 volatile 变量中读取时看到写入 - ”最终“在实践中几乎立即意味着”;-)
  • 实际上,如果您对应用程序运行速度最快的硬件或如何获得最高性能感兴趣,这一点很重要。我们希望从使用四路 Xeon (64 SMT) 中获得比最终更多的收益。如果您无法控制硬件或仅在单个套接字上运行,那可能不是问题,但如果早期知道并发实现细节以及它们如何影响大型机器上的可伸缩性,那么它们肯定会影响设计。
  • @RalfH 我使用volatile 关键字代表普通开发人员发言。一般来说,开发人员不需要担心 volatile 是如何实现的。当您知道底层架构改变了您将如何以不同的方式使用 volatile 时,您是否看到过一个实例?
  • :) 不,因为我也不知道硬件是怎么做的,所以我宁愿做分析。这就是我注意到 ConcurrentHashMap 中的 volatile get 对性能不利的原因,因此一旦它们的内容稳定,我们最终用 CopyOnWriteHashMaps 替换它们。如果您想知道即将发生的事情,硬件细节正在测试。就像我想知道 Haswell 中的硬件事务内存对 Java 并发意味着什么。
  • 好吧,这令人费解:) 我不确定我的理解是否正确:因为 x86 上的 LoadLoad 屏障是无操作的,所以 Marc 的程序生成了 StoreLoad 屏障? (这是以“lock”开头的行)。如果 volatile 读取产生了额外的指令(锁),为什么他说我们可以期望 volatile 读取是免费的?回到我最初的问题,如果我们在 Marc 的示例中添加了在读取 volatile 之后读取非易失性,那么“锁定”指令会发挥作用(确保非易失性的可见性)吗?
【解决方案2】:

如果 LoadLoad 的计算结果为无操作,则线程 2 可以继续使用缓存值。

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

编程顺序是

read b
read a
write a

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

read a
...
read b

禁止重新排序。

【讨论】:

  • 我实际上是指在 CPU 缓存中进行真正的缓存,而不是指令重新排序。
  • @zhong 我认为 Janek 关心的是,'如果一个字段在另一个处理器写入后从不启动加载,它如何在处理器的寄存器中保持最新'。
猜你喜欢
  • 1970-01-01
  • 2013-02-23
  • 2014-04-26
  • 2015-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-31
  • 2011-08-19
相关资源
最近更新 更多