什么时候使用volatile多线程?

时间:2010-12-29 21:24:03

标签: c++ multithreading concurrency atomic volatile

如果有两个线程访问全局变量,那么许多教程都说使变量volatile变为阻止编译器将变量缓存在寄存器中,因此无法正确更新。 但是,访问共享变量的两个线程是通过互斥锁来保护的东西不是吗? 但是在这种情况下,在线程锁定和释放互斥锁之间,代码处于一个关键部分,只有那个线程可以访问变量,在这种情况下变量不需要是volatile?

因此,多线程程序中volatile的用途/用途是什么?

4 个答案:

答案 0 :(得分:146)

短&快速回答volatile对于与平台无关的多线程应用程序编程(几乎)无用。它不提供任何同步,它不会创建内存栅栏,也不会确保操作的执行顺序。它不会使操作成为原子。它不会使您的代码神奇地保证线程安全。 volatile可能是所有C ++中最容易被误解的设施。有关volatile

的详情,请参阅thisthisthis

另一方面,volatile确实有一些使用可能不那么明显。它可以像使用const一样使用,以帮助编译器向您显示在以非受保护的方式访问某些共享资源时可能出错的位置。 Alexandrescu在this article中讨论了这种用法。但是,这基本上是以一种经常被视为设计的方式使用C ++类型系统,并且可以引起未定义的行为。

volatile专门用于与内存映射硬件,信号处理程序和setjmp机器代码指令连接时使用。这使volatile直接适用于系统级编程,而不是普通的应用程序级编程。

2003 C ++标准并没有说volatile对变量应用任何类型的Acquire或Release语义。实际上,标准对多线程的所有问题都完全保持沉默。但是,特定平台会在volatile变量上应用Acquire和Release语义。

[C ++ 11更新]

C ++ 11标准现在 直接在内存模型和语言中确认多线程,它提供了以独立于平台的方式处理它的库设施。但是volatile的语义仍然没有改变。 volatile仍然不是同步机制。 Bjarne Stroustrup在TCPPPL4E中说得很多:

  

除了直接处理的低级代码外,请勿使用volatile   用硬件。

     

不要认为volatile在内存模型中有特殊含义。它   才不是。它不是 - 在某些后来的语言中 - 一个   同步机制。要获得同步,请使用atomic,a   mutexcondition_variable

[/ End update]

以上都适用于C ++语言本身,如2003标准(现在的2011标准)所定义。但是,某些特定平台会为volatile添加额外的功能或限制。例如,在MSVC 2010(至少)中,获取和释放语义执行适用于volatile变量上的某些操作。 From the MSDN

  

优化时,编译器必须维护引用之间的顺序   to volatile对象以及对其他全局对象的引用。在   尤其是,

     

对volatile对象的写入(volatile write)具有Release语义;一个   引用在写入之前发生的全局或静态对象   指令序列中的volatile对象将在此之前发生   volatile编译在已编译的二进制文件中。

     

读取volatile对象(volatile read)具有Acquire语义;一个   引用读取后发生的全局或静态对象   之后将发生指令序列中的易失性存储器   在编译的二进制文件中读取volatile。

但是,您可能会注意到这样一个事实,即如果您按照上述链接进行评论,那么在这种情况下,评论中是否存在关于获取/发布语义实际是否适用的争论。 / p>

答案 1 :(得分:29)

由于以下原因,易失性偶尔会有用:此代码:

/* global */ bool flag = false;

while (!flag) {}

由gcc优化为:

if (!flag) { while (true) {} }

如果该标志由另一个线程写入,那么这显然是不正确的。请注意,如果没有这种优化,同步机制可能会起作用(取决于其他代码可能需要一些内存障碍) - 1个生产者 - 1个消费者场景中不需要互斥锁。

否则volatile关键字太奇怪而无法使用 - 它不提供任何内存排序保证兼容易失性和非易失性访问,并且不提供任何原子操作 - 即使用volatile关键字从编译器中得不到任何帮助禁用注册缓存。

答案 2 :(得分:1)

你需要不稳定并可能锁定。

volatile告诉优化器该值可以异步更改,因此

volatile bool flag = false;

while (!flag) {
    /*do something*/
}

每次循环都会读取标记。

如果关闭优化或使每个变量变为volatile,程序将表现相同但速度较慢。 volatile只是意味着'我知道你可能只是阅读它并知道它的内容,但如果我说读它就读它。

锁定是该计划的一部分。所以,顺便说一句,如果你正在实现信号量,那么它们必须是不稳定的。 (不要尝试它,它很难,可能需要一些小装配工或新的原子装置,而且它已经完成了。)

答案 3 :(得分:-1)

#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;

bool checkValue = false;

int main()
{
    std::thread writer([&](){
            sleep(2);
            checkValue = true;
            std::cout << "Value of checkValue set to " << checkValue << std::endl;
        });

    std::thread reader([&](){
            while(!checkValue);
        });

    writer.join();
    reader.join();
}

一次也认为volatile是无用的访问者与我争辩说,Optimization不会引起任何问题,并且指的是具有单独的高速缓存行以及所有其他内容的不同内核(实际上无法真正理解他所指的是什么) 。但是,这段代码在g ++(g ++ -O3 thread.cpp -lpthread)上用-O3编译时,显示出未定义的行为。基本上,如果在while检查之前设置该值,则它可以正常工作;否则,它会进入循环而不会费心去获取该值(该值实际上已由另一个线程更改)。基本上,我认为checkValue的值只会被提取一次到寄存器中,而不会在最高级别的优化下再次被检查。如果在获取之前将其设置为true,则它将正常工作,否则将进入循环。如果有错,请纠正我。