在C / C ++中,volatile变量是否保证最终在线程之间具有一致的语义?

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

要提供一个特定示例,请考虑共享变量v的两个线程,初始值为零。

线程1:v = 1

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

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

我知道C和C ++标准(在C ++ 0x之前)完全没有关于线程或并发的说法。 但我很好奇,如果C ++ 0x内存模型,或pthreads,或其他任何东西,保证这一点。 (显然这确实可以在Windows上运行32位x86;我想知道它是否可以依赖于某些东西,或者它是否恰好在那里工作)。

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

请注意,线程同步原语(如互斥锁)将根据需要执行必要的工作,但如果您只想确保状态可见而不关心一致性,则通常不需要线程同步原语 – 只需同步/刷新指令满足。

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

如果我已经正确理解了相关部分,C ++ 0X将不能保证它对于独立变量甚至是volatile变量(volatile不是为那个用途设计的),而是会引入primefaces类型,你将获得保证(参见header )。

首先,如果它没有标记为volatile,编译器很可能只加载一次。 因此无论内存最终是否发生变化,都无法保证编译会设置它。

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

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

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

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

在实践中,我无法想象它会如何失败。

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

如果变量不是volatile,则无法保证。 前C ++ 0x,标准对线程没有什么可说的,并且由于变量不是易失性的,因此读/写不被视为可观察的副作用,因此允许编译器作弊。 后C ++ 0x,它是一个竞争条件,明确声明是未定义的行为。

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

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

所以不,一般来说,即使使用volatile ,也无法保证正常工作。 它可能,而且可能经常会,但不总是(并且它取决于循环后发生的事情)。 这取决于编译器愿意采用优化的程度。 但允许它足够破坏代码。 所以不要依赖它。 如果要围绕这样的事情进行同步,请使用内存屏障。 这就是他们的目的。 (如果你这样做,你甚至不再需要volatile了)

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

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

既然你已经知道如何将变量放在两者都可以访问的地方,为什么不使用正确的工具来做一个不占用资源的等待? 一对pthread_mutex_tpthread_cond_t应该完美地解决这个问题。