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

时间:2010-06-24 21:46:25

标签: c++ c multithreading portability memory-model

是否有任何通常遵循的标准(ISO C或C ++,或 任何一个POSIX / SUS规范)变量(也许是 标记不稳定),没有被互斥锁保护,正在被访问 如果是多线程将最终保持一致 分配到?

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

主题1:   v = 1

主题2:   而(v == 0)     产率();

线程2是否保证最终终止?或者可以 可以想象,永远旋转,因为缓存一致性从未踢过 in并使得赋值在线程2的缓存中可见?

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

6 个答案:

答案 0 :(得分:12)

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

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

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

答案 1 :(得分:5)

如果我已经正确理解了相关章节,C ++ 0X将不能保证它对于独立变量甚至是易失性变量(volatile不是为那个用途设计的),而是会引入你将要使用的原子类型有保证(见标题<atomic>)。

答案 2 :(得分:3)

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

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

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

答案 3 :(得分:3)

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

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

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

答案 4 :(得分:2)

  

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

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

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

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

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

答案 5 :(得分:0)

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

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

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