2017-05-28 42 views
2

我有下面(源 - https://mechanical-sympathy.blogspot.in/2011/09/single-writer-principle.html)的发言几个澄清的:澄清单个写入器

“86/64有一个存储器模型,由此加载/存储存储器操作都保留命令,从而存储如果你严格遵守单一作者原则,就不需要屏障。

在x86/x64上,根据内存模型,可以使用较旧的存储重新排序“加载”,因此多个线程在多个内核之间改变相同的数据时需要内存屏障。 “

这是否意味着:
1.在一个单一的核心,加载/存储内存操作总是顺序?
因此,单个核心系统上的单个写入器线程(和多个读取器线程)不需要“同步”来解决可见性问题?
2.对于多核,可以重新安排负载,并从其他核心发起存储?
因此,单个编写器线程(以及运行在其他核心上的多个读取器线程)不需要“同步”来解决可见性问题(因为不会有任何存储)?因此,如果我们严格维护一个作者 - 我们实际上可以避免在原始锁中读取和写入时使用'synchronized'的做法。 我们实际上可以完全消除'synchronized'吗?

+1

您使用的是Java。您应该尊重Java内存模型。不是x86/64内存模型。 –

回答

1

在单个内核中,内存访问是按顺序还是乱序进行并不重要。如果有一个核心,它将始终感知一致的值,因为读取请求将由保存未写入数据的相同缓存提供。

但是,这对于Java程序来说是无关紧要的,因为作为Java语言规范一部分的Java内存模型并没有为多线程程序做出这样的保证。事实上,术语“记忆障碍”根本没有出现在规范中。

你必须认识到的是,你编写的Java代码不会是CPU将执行的x86/x64代码。优化的本地代码不会像你的源代码。代码优化的一个基本部分是消除冗余读写,甚至是条件代码部分,假设值之间不会发生虚假变化,这对单线程执行来说总是正确的。

如果由于没有正确的线程安全构造的多线程操作导致基础假设失效,此优化代码将产生不一致的结果。在规范中,这是一个可接受的不一致性,因为无论如何执行一致结果的内存模型都会导致性能显着下降。像同步或易失性写入和读取一样,线程安全结构不仅告诉JVM插入内存屏障的位置(如果底层体系结构需要它),还会告诉JVM何处以及如何限制代码优化。

这就是为什么a)在操作可变共享状态时需要正确的线程安全构造的原因,以及b)即使在CPU /硬件级别没有需要的内存屏障,这些构造可能会有性能损失。

+0

虽然你在这里,但就我所知,这是关于'lazySet'和'StoreLoad'的。 *是*,我们使用Java编码,但如果您处理Martin Thomson所做的数据,请考虑此问题 - 这对您有所帮助。他必须说的是,一个'StoreLoad'将强制缓冲区消耗 - 因此很昂贵,它是唯一必须在x86上实现并由运行时发布的。它本身不是'fence',而是具有相同效果的'lock addl'。由于x86是一种强大的内存模型,因此不需要其他防护。 – Eugene

+0

只有使用单个写入器原则才能释放缓冲区。因此这很便宜。我可能永远不会处理这个问题,就像* ever *;但我喜欢这些细节。这当然是我的理解 – Eugene

+1

@Eugene:这个问题显然是用[java]和[java-memory-model]标记的,因此,无论链接文章是关于什么的,我都回答* OP的问题* *不是*“关于'lazySet'和'StoreLoad'“,但是关于由于目标硬件体系结构而写破碎的代码的诱惑。 – Holger

0

大免责声明

一些我在这里写的事情实际上测试 - 就像重新排序,冲洗等;他们中的一些人花了很多时间阅读,我希望我说得对。

一切被重新排序,而不是重新排序的策略,让你的程序运行它在数年前下降了道路。只要输出不改变,操作就会按照他们的要求重新排序。

例如:

static int sum(int x, int y){ 
    x = x + 1; 
    y = y + 1; 
    return x + y; 
} 

你真的不关心为了在实现这些工作,只要结果是正确的,你做了什么?

没有内存屏障(通常称为StoreLoad|StoreStore|LoadStore|LoadLoad),任何操作都可能发生变化。为了保证有些操作不需要move beyond a fence,有cpu fences执行。 Java有几种生成方法 - volatile,synchroniztion,Unsafe/VarHandle(可能有其他的,我不知道)。

基本上当你写一个volatile例如,出现这种情况:

volatile x... 

[StoreStore] - inserted by the compiler 
[LoadStore] 
x = 1; // volatile store 
[StoreLoad] 

... 

[StoreLoad] 
int t = x; // volatile load 
[LoadLoad] 
[LoadStore] 

让我们这个例子的一个子集:

​​

这意味着任何Store或的Load变量不能用x = 1重新排序。同样的原则适用于其他障碍。

马丁汤姆森说,那是什么on x864分之3的屏障是免费的,已被发出的只有一个:StoreLoad。它们是免费的,因为x86具有强大的内存模型,这意味着其他操作默认情况下不会重新排序。在其他的CPU上,其中一些操作也相当便宜(如果我在ARM上有错误lwsync - 轻量级同步;名称应该是自解释的)。

此外,CPU和缓存之间还有一点缓冲区 - 称为Store Buffer。当您将某些内容写入变量时,它不会直接进入缓存(s)。它进入该缓冲区。当它已满(或被强制通过StoreLoad排空)时,它会将写入写入缓存 - 并且最多可以使用cache coherency protocol来同步所有缓存中的数据。

马丁说,如果你有多个作家,你必须多次发出StoreLoad - 因此它是昂贵的。如果你有一个作家,你不需要。缓冲区满时会排空。什么时候发生?那么有时,理论上可能永远不会,实际上相当快。

一些很棒的资源(这些资源有时让我整夜都没有睡觉,所以小心!):

这些一StoreStore顺便说一句你写的每一个最终的变量在构造函数中时间:

private final int i ; 

public MyObj(int i){ 
    this.i = i; 
     // StoreStore here 
} 

LazySet

Shipilev Volatile

And my all time favorite!