【问题标题】:Is it useful to mark variables as volatile if they are shared across threads? [duplicate]如果跨线程共享变量,将它们标记为 volatile 是否有用? [复制]
【发布时间】:2015-05-20 09:37:17
【问题描述】:

注意!

我显然没有向这里的每个人清楚地表达我的观点,这令人难以置信的沮丧。我的目标是消除volatile 实际上是一个无操作的神话,它什么都不做。我并不是要说它应该被使用,它是必不可少的,它不是多余的,等等。

我已经证明volatile 仍然可以做某事。我承认在某些情况下它是多余的,并且多线程示例是一个糟糕的选择。

我也不想隐瞒我的答案的初始修订包含错误的事实。但这个问答甚至还没有达到预期目的。为此,我认为是时候把它扔掉了。

感谢 Kerrek 和 T.C.为他们的见解。我只是不认为他们的回答符合我想问的问题。我敢肯定这是我问得不好的错。

所以我放弃了!并把它作为问题的副本关闭,而不是它的意图,而是它已被解释为。

干杯! (&hth.)

我正在一个线程中写入一个变量并在另一个线程中读取它。有人告诉我 volatile 对此完全没用,除非我使用硬件,否则我不需要在这个时代使用它。

int x = 0;
void thread1()
{
   while (true) {
      sleep(1);
      if (x > 0)
         break;
   }
}

void thread2()
{
   while (true) {
      sleep(1);
      x++;
   }
}

在这种情况下使用volatile 有什么好处吗?
如果x 不是一个简单的int 而是一个类类型呢?

【问题讨论】:

  • 注意:这个问题是由一些激烈的争论引起的。关于volatile 的一般主题有几个问题,但我觉得没有人真正专门针对volatile 在现代多线程代码中没有位置的误解。但是,我在回答中引用了两个部分相关的现存问题。
  • @LightnessRacesinOrbit 当然volatile 在现代多线程代码中占有一席之地!即使这样的代码至少需要与编译器做出的有保证的非优化假设进行交互。无论是硬件行为,还是某些东西的无锁实现。
  • 附带说明,volatile 通常与可以自行更改的外围寄存器一起使用。例如,您的代码可能需要像while(STATUS & 0x80); 这样的自旋循环(其中STATUS 类似于volatile uint8_t& STATUS{*reinterpret_cast<uint8_t*>(0x40F00010)};,并且您不希望它优化为if(STATUS & 0x80) while(1);,如果STATUS 不是volatile,就会发生这种情况并且编译器决定将它读入一次寄存器,然后不再获取它。与驱动程序一起玩。
  • 我从来没有遇到过它什么都不做的神话。我遇到的唯一神话是它可以用来避免线程之间的数据竞争。可移植,它不能(尽管在某些平台上会出现这种情况),并且尝试这样做只会给出更微妙的未定义行为。 (在这种情况下,如果x 在修改期间短暂地取了一个无效值,或者如果对一个线程的内存视图的更改从未传播到另一个线程,它可能会失败。)
  • isvolatileusefulwiththreads.com(点击C++有几个权威参考)

标签: c++ multithreading c++11 volatile


【解决方案1】:

你被正确地告知,volatile 对线程间通信没有用处。不要将其用于此目的。它不提供同步,并使您的代码存在数据竞争。反之,正确同步访问共享状态时,不需要volatile

Does What You Mean 的正确代码对共享状态使用原子变量或使用互斥锁保护共享状态,所有线程都将正确观察到共享状态。例如:

#include <atomic>

std::atomic<int> x = 0;

void thread1()
{
   while (true) {
      sleep(1);
      if (x > 0)
         break;
   }
}

void thread2()
{
   while (true) {
      sleep(1);
      x++;
   }
}

在任何时候都不需要volatile

请注意,volatile 可能在线程中很有用,可以强制执行一个没有副作用的循环:

// Spend some time
for (volatile int i = 0; i != LARGE_NUMBER; ++i)
{ /* do nothing */ }

// or even:
for (int i = 0; const_cast<volatile int &>(i) != LARGE_NUMBER; ++i) {}

【讨论】:

  • @LightnessRacesinOrbit 除非您添加了一个误解。所问的问题是关于跨线程的可变访问,而您(或其他任何人)尚未发布在这种情况下需要它们的示例。
  • @LightnessRacesinOrbit 它们不是跨线程共享所必需的。它们不足以跨线程共享。那么,除了作为一种并发症和悲观情绪之外,为什么要把它们放进去呢?
  • @MattMcNabb 不,你没有标记它volatile。您只需编写适当的同步就足够了。 (在&lt;atomic&gt; 之外,没有标准库类有volatile 成员函数,所以如果你写一个volatile std::string,你几乎无能为力。)
  • @MattMcNabb 同步操作。 (给定 write-unlock-lock-read,前两个操作发生在一个线程上,后两个操作发生在不同的线程上,解锁 锁同步,所以写 发生在 读取,并假设没有干预写入,通过 [intro.multithread]/p15 读取必须读取写入写入的内容。)
  • @MattMcNabb:我认为你是这篇文章的主要受害者。不,volatile不是必要的; C++ 内存模型和对库实现的后续要求保证使用互斥锁提供正确的可见性。
猜你喜欢
  • 2019-04-01
  • 2015-08-27
  • 1970-01-01
  • 2015-06-17
  • 1970-01-01
  • 2011-06-23
  • 1970-01-01
  • 2018-06-16
相关资源
最近更新 更多