10

是否有任何普遍遵循的标准(ISO C 或 C++,或任何 POSIX/SUS 规范)保证一个变量(可能标记为 volatile),不受互斥锁保护,被多个线程访问,最终将变得一致如果它被分配到?

举一个具体的例子,考虑两个线程共享一个变量 v,初始值为零。

线程 1:v = 1

线程 2:while(v == 0) yield();

线程 2 是否保证最终终止?或者它可以想象永远旋转,因为缓存一致性永远不会启动并使分配在线程 2 的缓存中可见?

我知道 C 和 C++ 标准(在 C++0x 之前)根本没有谈到线程或并发性。但我很好奇 C++0x 内存模型、pthreads 或其他任何东西是否能保证这一点。(显然,这确实适用于 32 位 x86 上的 Windows;我想知道它是否是可以普遍依赖的东西,或者它是否恰好在那里工作)。

4

6 回答 6

12

这将取决于您的架构。虽然要求显式缓存刷新或内存同步以确保内存写入对其他线程可见是不常见的,但没有什么能阻止它,而且我肯定遇到过明确指令的平台(包括我目前正在开发的基于 PowerPC 的设备)必须执行以确保刷新状态。

请注意,像互斥锁这样的线程同步原语将根据需要执行必要的工作,但如果您只想确保状态可见而不关心一致性,您通常实际上不需要线程同步原语 - 只需同步/刷新指令即可够了。

编辑:对于仍然对volatile关键字感到困惑的任何人-保证编译器不会volatile生成将数据显式缓存在寄存器中的代码,但这与处理透明缓存/重新排序读取和写入的硬件不同。阅读例如thisthis,或者Dobbs 博士的文章,或者这个 SO 问题的答案,或者只是选择你最喜欢的编译器,它针对像 Cell这样的弱一致的内存架构,编写一些测试代码并将编译器生成的内容与你的内容进行比较' d 需要以确保写入对其他进程可见。

于 2010-06-24T21:52:12.723 回答
5

如果我对相关部分的理解正确,C++0X 不会保证它适用于独立变量甚至 volatile 变量(volatile 不是为这种用途而设计的),但会引入原子类型,您将获得保证(见标题<atomic>)。

于 2010-06-25T09:27:38.150 回答
3

首先,如果它没有被标记为 volatile,那么编译器很有可能只加载一次。所以不管内存最终是否改变,都不能保证编译器会设置它。

由于您明确地说“没有互斥体”,因此 pthreads 不适用。

除此之外,由于 C++ 没有内存模型,它取决于硬件架构。

于 2010-06-24T21:51:41.353 回答
3

这是一场潜在的数据竞赛。

关于 POSIX 线程,这是 UB。我相信与 C++ 相同。

在实践中,我无法想象它怎么会失败。

于 2011-10-02T05:23:20.077 回答
2

线程 2 是否保证最终终止?或者它可以想象永远旋转,因为缓存一致性永远不会启动并使分配在线程 2 的缓存中可见?

如果变量不是易失性的,则您无法保证。在 C++0x 之前,标准对线程只字未提,而且由于变量不是易失的,读/写不被认为是可观察到的副作用,因此允许编译器作弊。在 C++0x 之后,这是一个竞争条件,它被明确声明为未定义的行为。

如果变量是易失性的,则可以保证发生读/写,并且编译器不会相对于其他易失性内存访问重新排序。(但是,这本身并不能保证 CPU 不会重新排序这些内存访问——只是编译器不会)

但是您不能保证它不会针对其他非易失性访问重新排序,因此您可能无法获得预期的行为。特别是,如果编译器认为这样做安全(并且有益),那么您试图“保护”的 while 循环之后的一些指令可能会被移到循环之前。但在执行此分析时,它只查看当前线程,而不查看其他线程中发生的情况。

所以不,一般来说,即使使用volatile. 它可能会,而且可能经常会,但并非总是如此(这取决于循环之后发生的情况)。这取决于编译器愿意在多大程度上进行优化。但它被允许走得足够远以破坏代码。所以不要依赖它。如果你想围绕这样的东西进行同步,请使用内存屏障。这就是他们的目的。(如果你这样做,你甚至不再需要volatile

于 2010-06-25T09:57:00.570 回答
0

我认为它最终会在任何平台上运行,但不知道你可能会看到的延迟。

但老实说,轮询等待事件真的很糟糕。即使你做了一个,yield你的过程也会一次又一次地重新安排,而不做任何事情。

既然您已经知道如何将变量放置在两者都可以访问的地方,为什么不使用正确的工具来执行不占用资源的等待呢?一对pthread_mutex_t并且pthread_cond_t应该完美地做到这一点。

于 2010-06-25T07:55:34.190 回答