内存一致性错误与线程干扰

2022-09-03 01:30:18

内存一致性错误和线程干扰之间有什么区别?使用同步来避免它们有何不同?请举例说明。我无法从sun Java教程中得到这个。任何阅读材料以纯粹在java上下文中理解这一点的建议都会有所帮助。


答案 1

内存一致性错误不能纯粹在java的上下文中理解 - 多CPU系统上共享内存行为的细节是高度特定于架构的,更糟糕的是,与从一开始就为多处理器机器设计的架构(如POWER和SPARC)相比,x86(今天大多数人编码学习编码的地方)具有非常程序员友好的语义。 所以大多数人真的不习惯考虑内存访问语义。

我将给出一个常见示例,说明内存一致性错误可能会给您带来麻烦。对于此示例,假设 的初始值为 3。几乎所有的架构都保证,如果一个CPU执行代码:x

STORE 4 -> x     // x is a memory address
STORE 5 -> x 

和另一个 CPU 执行

LOAD x
LOAD x

将看到、 、 、 或 从其两个指令的角度来看。基本上,CPU保证从所有CPU的角度来看,对单个内存位置的写入顺序保持不变,即使允许其他CPU知道每个写入的确切时间。3,33,44,44,55,5LOAD

CPU彼此不同的地方往往在于它们所做的保证以及涉及不同内存地址的操作。对于此示例,假设两者的初始值和的初始值均为 4。LOADSTORExy

STORE 5 -> x   // x is a memory address
STORE 5 -> y // y is a different memory address

然后另一个 CPU 执行

LOAD x
LOAD y

在此示例中,在某些体系结构上,第二个线程可以看到 、 、 、 OR 。哎哟!4,45,54,55,4

大多数体系结构以 32 位或 64 位字的粒度处理内存 - 这意味着在 32 位 POWER/SPARC 计算机上,如果没有显式同步,则无法更新 64 位整数内存位置并安全地从另一个线程读取它。高飞,是吧?

线程干涉要简单得多。基本思想是java不能保证Java代码的单个语句以原子方式执行。例如,递增一个值需要读取该值,递增该值,然后再次存储该值。所以你可以在两个线程执行之后,最终可以作为或取决于低级代码如何交错(这里工作的低级抽象代码大概看起来像)。这里的基本思想是,Java代码被分解成更小的原子片段,除非你明确使用同步原语,否则你不能对它们如何交错做出假设。int x = 1x++x23LOAD x, INCREMENT, STORE x

有关详细信息,请查看白皮书。它又长又干,由一个臭名昭著的混蛋写的,但嘿,它也很好。也看看这个(或者只是谷歌“双重检查锁定已损坏”)。这些内存重新排序问题为许多C++/java程序员带来了丑陋的头颅,他们几年前试图在单例初始化时变得有点太聪明了。


答案 2

线程干扰是关于线程覆盖彼此的语句(例如,线程 A 递增计数器,线程 B 同时递减计数器),从而导致计数器的实际值不可预测的情况。您可以通过强制执行独占访问(一次一个线程)来避免它们。

另一方面,内存不一致与可见性有关。线程 A 可能会递增,但线程 B 可能还没有意识到此更改因此它可能会读取一些先前的值。您可以通过建立先发生关系来避免它们,即counter

只是保证一个特定语句写入的内存对另一个特定语句可见。(每个甲骨文)


推荐