内存不一致与线程交错有何不同? [英] How is memory inconsistency different from thread interleaving?

查看:130
本文介绍了内存不一致与线程交错有何不同?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个多线程程序,正在研究是否应该使用 volatile 作为我的布尔标志。关于并发的文档oracle跟踪没有解释任何关于内存一致性错误除了:


当不同的线程具有
不一致的应该是相同数据的视图时,会发生内存一致性错误。


它使得假设这些不一致的视图仅在写入操作之后发生。但是多久之后?



示例1

 线程A:检索标志。 
线程B:检索标志。
线程A:取消检索值;结果是真的。
线程A:将结果存储在标志中;旗帜现在是真的。
线程B:System.out.print(flag) - > false

由于线程A 线程B 并发运行,print也可以为true,具体取决于它何时检索 flag 。这对于不一致性来说是完全合理的。



但是描述了内存一致性错误的方式(写入变量是不一定反映在其他线程中)听起来也是如此:



示例2

 线程A:检索标志。 
线程A:更改检索值;结果是真的。
线程A:将结果存储在标志中;旗帜现在是真的。
//任何更长的时间过去(虽然线程A没有显式发生 - 在线程B之前,显然它确实发生了。)
线程B:检索标志。
线程B:System.out.print(flag) - > true或false(不可预测)

我强烈认为示例2不能为真。问题是,只有当 为真时才能看到使用 volatile 来建立之前发生的事情



如果是真的,为什么会这样?如果不是..为什么要使用 volatile 呢?

解决方案

要了解JVM内存模型最具挑战性的事情之一是严格来说,计时(即,您的挂钟)完全不相关。



无论多长时间(根据您的挂钟)时间间隔过去2在2个独立线程中的操作,如果没有发生在之前的关系,则绝对不能保证每个线程在内存中看到的内容。






示例2 中,您提到的棘手部分是,


虽然线程A没有明确发生 - 在线程B之前,显然




从上面的描述来看,唯一可以说是显而易见的是,根据您的挂钟测量的时间,一些操作发生以后比其他人。但 并不意味着在严格意义上的JVM内存模型中发生了 关系。






让我展示一组与您上面的示例2的描述兼容的操作(即,根据您的挂钟所做的测量),这可能会导致 false ,不能保证。




  • 主线程M启动线程A和线程B :在线程M和线程A之间以及线程M和线程B之间存在 发生在关系之前 。因此,如果没有其他事情发生,线程A和线程B将看到与该布尔值的线程M相同的值。我们假设它被初始化为 false (以使其与您的描述兼容)。



假设您在多核计算机上运行。此外,假设线程A在Core 1中分配,线程B在Core 2中分配。




  • 线程A读取布尔值:它必须读取 false (参见上一个要点)。当读取发生时,可能发生某些内存页面(包括包含该布尔值的内存页面)将被缓存到Core 1的L1缓存或L2缓存中 - 该特定内核本地的任何缓存。 / p>


  • 线程A否定并存储布尔值:它将存储 true 现在。但问题是:在哪里?在发生 - 之前发生之前,线程A可以自由地将此新值存储在运行该线程的Core的本地缓存中。因此,该值可能会在Core 1的L1 / L2缓存上更新,但在处理器的L3缓存或RAM中将保持不变。


  • 经过一段时间(根据您的挂钟),线程B读取布尔值:如果线程A未将更改刷新为L3或RAM,则完全有可能线程B将读取 false 。另一方面,如果线程A刷新了更改,则可能线程B将读取 true (但仍无法保证 - 线程B可能已经收到了线程M的内存视图的副本,由于之前没有发生,它不会再次进入RAM并仍然会看到原始值。




保证任何事情的唯一方法是在之前发生明确的 > :它会强制线程A刷新其内存,并强制线程B不从本地缓存读取,而是从权威源读取它。



如果没有事先发生,你可以从上面的例子中看到,任何事情都可能发生,无论在不同线程中的事件之间经过多少时间(从你的角度来看)。






现在,最大的问题是:为什么 volatile 解决示例2中的问题



如果t hat boolean变量标记为 volatile ,如果根据上面的例2(即从挂钟的角度来看)进行交错操作,那么,只有那时,Thread B保证看到 true (即,根本没有任何保证)。



原因是 volatile 有助于建立先发生过的关系。它如下:写入 volatile 变量 - 在对同一变量的任何后续读取之前发生。



因此,通过标记变量 volatile ,如果从时序角度看,线程B仅在线程A更新后读取,则线程B保证看到更新(从内存一致性的角度来看)。



现在有一个非常有趣的事实:如果线程A对非易失性变量进行了更改,那么更新一个volatile变量,然后更新(从挂钟的角度来看)线程B读取那个volatile变量,它也保证线程B会看到非易失性变量的所有变化!这是由非常复杂的代码使用,它们希望避免锁定并且仍然需要强大的内存一致性语义。它通常被称为易变量捎带






作为最后的评论,如果你试图模拟(缺乏)发生在关系之前,它可能令人沮丧......当你把东西写到控制台时(即 System.out.println ),JVM可能会在多个不同的线程之间进行大量的同步,因此很多内存实际上可能会被刷新,并且你不一定能够看到你正在寻找的效果......这很难模拟这一切!


I'm writing a multi-thread program and was researching if I should use volatile for my boolean flag. The docs oracle trail on concurrency doesn't explain anything about memory consistency errors other than:

Memory consistency errors occur when different threads have inconsistent views of what should be the same data.

It makes sense to assume that these inconsistent views only occur after a 'write'-operation. But how long after?

EXAMPLE 1

Thread A: Retrieve flag.
Thread B: Retrieve flag.
Thread A: Negate retrieved value; result is true.
Thread A: Store result in flag; flag is now true.
Thread B: System.out.print(flag) --> false

Since Thread A and Thread B runs concurrently, the print could also result in true, depending on when it retrieved flag. That makes perfect sense as for inconsistency.

But the way memory consistency errors are described (writes to a variable are not necessarily reflected in other threads) it sounds like this is also true:

EXAMPLE 2

Thread A: Retrieve flag.
Thread A: Change retrieved value; result is true.
Thread A: Store result in flag; flag is now true.
//any longer amount of time passes (while Thread A didn't explicitly happen-before Thread B, it obviously did.)
Thread B: Retrieve flag.
Thread B: System.out.print(flag) --> true OR false (unpredictable)

I strongly assume that example 2 cannot be true. The problem is that only if it is true can I see the use of volatile to establish a happens-before.

If it is true, why is it so? If it's not.. why use volatile at all?

解决方案

One of the most challenging things to understand about the JVM memory model is that, strictly speaking, timing (ie, your wall clock) is completely irrelevant.

No matter how long (according to your wall clock) time elapses between 2 operations in 2 separate threads, if there isn't a happens-before relationship, there are absolutely no guarantees as to what each thread will see in the memory.


In your Example 2, the tricky part is that you mention,

while Thread A didn't explicitly happen-before Thread B, it obviously did.

From the description above, the only thing that you can say is obvious is that, according to the time measurements from your wall clock, some operations happened later than others. But that doesn't imply a happens-before relationship in the strict sense of the JVM memory model.


Let me show a set of operations that are compatible with your description of Example 2 above (ie, according to the measurements made by your wall clock) and that could result in either true or false, and no guarantees can be made.

  • The Main Thread M starts Thread A and Thread B: there's a happens-before relationship between Thread M and Thread A, and between Thread M and Thread B. For this reason, if nothing else happens, both Thread A and Thread B will see the same value as Thread M for that boolean. Let's assume it was initialized as false (to keep it compatible to your description).

Assume you are running on a multi-core machine. Also, assume Thread A was allocated in Core 1, and Thread B was allocated in Core 2.

  • Thread A reads the boolean value: it will necessarily read false (see the previous bullet point). When this read happens, it might happen that some memory pages, including the one containing that boolean, will be cached into Core 1's L1 cache, or L2 cache -- any cache local to that specific core.

  • Thread A negates and stores the boolean value: it will store true now. But the question is: where? Until a happens-before occur, Thread A is free to store this new value only on the local cache to the Core running the thread. Therefore, it is possible that the value will be updated on Core 1's L1/L2 cache, but will remain unchanged in the processor's L3 cache or in RAM.

  • After some time (according to your wall clock), Thread B reads the boolean value: if Thread A didn't flush the changes into L3 or RAM, it's entirely possible that Thread B will read false. If Thread A, on the other hand, had flushed the changes, it is possible that Thread B will read true (but still not guaranteed -- Thread B might have received a copy of Thread M's view of the memory and, due to the lack of happens-before, it won't go to the RAM again and will still see the original value).

The only way to guarantee anything is to have an explicit happens-before: it would force Thread A to flush its memory, and would force Thread B to read not from a local cache, but truly read it from the "authoritative" source.

Without a happens-before, as you can see form the example above, anything can happen, no matter how much time (from your perspective) elapses between events in different threads.


Now, the big question: why would volatile solve the issues in your Example 2?

If that boolean variable is marked as volatile, and if the interleaving of operations happens according to your Example 2 above (ie, from a wall clock perspective), then, only then, Thread B is guaranteed to see true (ie, otherwise there are no guarantees at all).

The reason is that volatile helps establish happens-before relationships. It goes as follows: a write to a volatile variable happens-before any subsequent read to the same variable.

Thus, by marking the variable volatile, if from a timing perspective Thread B reads only after Thread A updates, then Thread B is guaranteed to see the update (from a memory consistency perspective).

One very interesting fact now: if Thread A makes changes to non-volatile variables, then updates a volatile variable, then later (from a wall clock perspective) Thread B reads that volatile variable, it's also guaranteed that Thread B will see all the changes to the non-volatile variables! This is used by very sophisticated code that wants to avoid locks and still requires strong memory consistency semantics. It's typically called volatile variable piggybacking.


As a final remark, if you try to simulate the (lack of) happens-before relationships, it might be frustrating... When you write things out to the console (ie, System.out.println), the JVM might do a lot of synchronization between multiple different threads, so a lot of memory might actually get flushed, and you won't necessarily be able to see the effects you are looking for... It's very hard to simulate all this!

这篇关于内存不一致与线程交错有何不同?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆