【问题标题】:Thread visibility among one process一个进程之间的线程可见性
【发布时间】:2013-04-22 00:09:12
【问题描述】:

我最近在看Crack Code Interview这本书,但是第257页有一段让我很困惑:

线程是进程的特定执行路径;当一个线程修改进程资源时,该更改立即对兄弟线程可见。

IIRC,如果一个线程对变量进行了更改,更改将首先保存在 CPU 缓存中(例如 L1 缓存),并且除非变量声明为 volatile,否则不会保证与其他线程同步。

我说的对吗?

【问题讨论】:

  • 如果你在谈论 volatile,你也需要说明语言。在 C 和 C++ 中,volatile 对于线程同步通常没用,而在 Java 中它很有用。
  • @doomster,谢谢,但我相信volatile 在 Java 和 C++ 中扮演同样的角色,你能解释一下为什么它与你的不同吗?
  • 它们的意图相似,但不等同。在 Java 中,您对它们在多线程程序中的行为有非常具体的保证。对于 C++,请参阅 stackoverflow.com/q/2484980/1968182
  • @MrROY:在 C 和 C++ 中,volatile 没有关于线程的定义语义。在 Java 中,确实如此。在 C 或 C++ 中没有线程同步情况,volatile 就足够了,而在 Java 中,有。

标签: multithreading operating-system thread-safety


【解决方案1】:

不,你错了。但这是一个非常常见的误解。

每个现代多核 CPU 都有硬件 cache coherence。 L1 和类似的缓存是不可见的。像 L1 缓存这样的 CPU 缓存与内存可见性无关

当线程修改进程资源时,更改立即可见。问题在于优化导致进程资源不能精确地按照代码指定的顺序进行修改。

如果您的代码有k = j; i = 4; if (j == 2) foo();,优化器可能会看到您的第一个赋值读取j 的值。因此,当您将其与 2 进行比较时,它可能不会再阅读它,因为它“知道”它不能改变。但是,另一个线程可能已经改变了它。因此,当需要线程之间的同步时,需要禁用某些类型的优化。这就是volatile 之类的事情。

如果编译器和 CPU 不做任何优化并按照编写的程序执行程序,则永远不需要 volatile。内存可见性是关于代码优化(一些由编译器完成,一些由 CPU 完成),而不是缓存。

【讨论】:

  • “CPU 缓存 .. 与内存可见性无关” - 为什么?许多文本表明并非如此。例如,来自 JMM FAQ(cs.umd.edu/users/pugh/java/memoryModel/…):“在我们退出同步块后,我们释放监视器,这具有将缓存刷新到主内存的效果,因此该线程所做的写入可以被其他人看到线程”
  • @EyalSchneider:JMM 不是在谈论任何实际的实现,而是在谈论一个可能做任何事情的概念系统。 如果您的缓存与硬件不一致,则必须刷新它们。请注意,它说它“具有将缓存刷新到主内存的效果”,而不是它实际上刷新了缓存。 (而且,在实践中,根本不会刷新缓存。只使用内存屏障绕过某些 CPU 优化,例如推测性获取和发布写入。)
  • 谢谢大卫,正如你所说,如果一个线程读取j,然后另一个线程更改它,第一个线程不会注意到它。所以我引用的文字实际上是错误的?
  • 你能解释一下volatile 在你给出的代码示例中是如何工作的吗?
  • @MrROY:如果第二个线程实际上更改了它,并且第一个线程实际上读取了它,那么第二个线程会注意到它。问题是优化可能意味着第二个线程实际上并没有改变它,或者第一个线程实际上并没有读取它。
【解决方案2】:

我认为您引用的文字不正确。 Java 内存模型的整个想法是处理现代软件和硬件的复杂优化,以便程序员可以确定哪些写入在其他线程中的相应读取中可见。

除非 Java 程序正确同步,否则您不能保证一个线程的更改立即对其他线程可见。也许文本指的是一个非常具体(和弱)的内存模型。

使用volatile变量只是线程同步的一种方式,并不适合所有场景。

--编辑--

我想我现在理解了这种困惑......我同意 David Schwartz 的观点,假设:

1) “修改进程资源”是指资源的实际更改,而不仅仅是执行以某种高级计算机语言编写的写指令。

2) “对同级线程立即可见”表示其他线程能够看到它;这并不意味着程序中的线程一定会看到它。您可能仍需要使用同步工具来禁用绕过对资源的实际访问的优化。

【讨论】:

  • 你是对的,也是对的。您是对的,必须处理现代软件和硬件的复杂优化。但他问的是 CPU 缓存,而不是“复杂的优化”。高速缓存一致性硬件使 CPU 高速缓存不可见。事实上,在现代 Intel CPU 上,L3 缓存的主要目的是加速内核间操作(在几乎所有多核 CPU 上,L2 缓存的次要目的就是这样做)。如果您必须解决它们或刷新它们以在线程之间同步,它们会使它们打算改进的事情之一变得更糟。
  • @DavidSchwartz:我并没有在我的回复中谈论特定的缓存。我只是指出问题中引用的文字不正确且具有误导性。
  • @DavidSchwartz:好的,我明白了。这取决于您如何解释“修改进程资源”。 “修改变量”和“修改主内存”之间存在差异。后者不一定是前者的直接结果。
  • 没错。而且我同意,可能大多数程序员读到它会认为它是不正确的,或者更糟糕的是,被误导认为它是在说内存可见性不是线程之间的问题。 (这是一个非常微妙的问题,很少有程序员理解,远少于他们认为他们理解它的数字。)
  • @DavidSchwartz,仅出于一般兴趣:PS3 中的 Cell 处理器是不可见的“缓存”的罕见示例。我说的是 8 个 SPE 上的 SRAM。作为一名程序员,一个人全权负责加载/卸载该内存。它会导致复杂的编程,但如果做对了,你可以获得巨大的性能。从这个意义上说,Cell 具有简单的硬件(即没有缓存控制硬件)和复杂的软件。 Intel 和 AMD CPU 具有复杂的缓存硬件,以支持简单的软件。猜猜哪种方法卖得更好...... Cell 是 IBM 的一次勇敢尝试,我喜欢它。
猜你喜欢
  • 2016-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多