C++ 何时将volatile与多线程一起使用?
如果有两个线程访问一个全局变量,那么许多教程都说,让该变量可变,以防止编译器将该变量缓存在寄存器中,从而无法正确更新该变量。 然而,两个线程都访问一个共享变量,这需要通过互斥来进行保护,不是吗? 但是在这种情况下,在线程锁定和释放互斥锁之间,代码处于一个关键部分,其中只有一个线程可以访问变量,在这种情况下,变量不需要是可变的 因此,volatile在多线程程序中的用途是什么(编者按:在C++11中C++ 何时将volatile与多线程一起使用?,c++,multithreading,concurrency,atomic,volatile,C++,Multithreading,Concurrency,Atomic,Volatile,如果有两个线程访问一个全局变量,那么许多教程都说,让该变量可变,以防止编译器将该变量缓存在寄存器中,从而无法正确更新该变量。 然而,两个线程都访问一个共享变量,这需要通过互斥来进行保护,不是吗? 但是在这种情况下,在线程锁定和释放互斥锁之间,代码处于一个关键部分,其中只有一个线程可以访问变量,在这种情况下,变量不需要是可变的 因此,volatile在多线程程序中的用途是什么(编者按:在C++11中volatile不是这个工作的合适工具,仍然有数据竞争UB。使用std::atomic和std::m
volatile
不是这个工作的合适工具,仍然有数据竞争UB。使用std::atomic
和std::memory\u order\u released
加载/存储来完成这项工作,而不使用UB。在实际实现中,它将编译为与volatile
相同的asm。我更详细地补充了一点同时也解决了在这种情况下,弱有序内存可能是问题的误解:所有真实世界的CPU都有一致的共享内存,所以代码>易失性>代码>将在实际C++实现中起作用。但仍然不这样做。
评论中的一些讨论似乎在讨论其他用例,在这些用例中,您需要比放松原子更强大的东西。这个答案已经指出,volatile
不给您排序。)
Volatile有时很有用,原因如下:此代码:
/* global */ bool flag = false;
while (!flag) {}
由gcc优化,以:
if (!flag) { while (true) {} }
如果标志由另一个线程写入,这显然是不正确的。请注意,如果没有此优化,同步机制可能会工作(取决于其他代码,可能需要一些内存障碍)-在1生产者-1消费者场景中不需要互斥
否则volatile关键字太奇怪,无法使用-它不提供任何内存排序保证wrt volatile和non-volatile访问,也不提供任何原子操作-也就是说,使用volatile关键字的编译器除了禁用寄存器缓存之外,没有任何帮助。Short&quick answer:
易失性
is(接近)对平台无关的多线程应用程序编程毫无用处。它不提供任何同步,不创建内存围栏,也不确保操作的执行顺序。它不会使操作原子化。它不会使代码神奇地实现线程安全。volatile
可能是最容易被误解的一种所有的C++中的OD设施。参见,并了解更多关于“代码>易失性< /代码> < /P>的信息。
另一方面,volatile
确实有一些不太明显的用途。它的使用方法与使用const
来帮助编译器向您显示在以非保护方式访问某些共享资源时可能会犯错误的地方是一样的。Alexandrescu在年讨论了这种用法。然而,这是一种错误SIC使用C++类型的系统,通常被看作是一种工具,可以引起未定义的行为。
volatile
专门用于与内存映射硬件、信号处理程序和setjmp机器代码指令接口时。这使得volatile
直接适用于系统级编程,而不是正常的应用程序级编程
<> 2003 C++标准并没有说volatile
的语义仍然没有改变。volatile
仍然不是一种同步机制。比亚恩·斯特劳斯鲁普说TCPPPL4E中有很多:
不要使用volatile
,直接交易的低级代码除外
用硬件
不要假设volatile
在内存模型中有特殊意义。它
没有。它不像在后来的一些语言中那样是
同步机制。要获得同步,请使用原子
互斥锁
,或条件变量
[/End update]
以上都适用于C++语言本身,如2003标准(现在是2011标准)所定义的。然而,一些特定的平台确实对代码< > < < />代码>添加了额外的功能或限制。例如,在MSVC 2010中(至少)获取和释放语义确实适用于
volatile
变量的某些操作:
优化时,编译器必须保持引用之间的顺序
到易失性对象以及对其他全局对象的引用
特别是
对易失性对象的写入(volatile write)具有释放语义
对写入前发生的全局或静态对象的引用
指令序列中的易失性对象将在此之前出现
易失性写入已编译的二进制文件
对易失性对象的读取(volatile read)具有获取语义
对读取后发生的全局或静态对象的引用
指令序列中的易失性存储器将在此之后出现
在已编译的二进制文件中进行易失性读取
但是,您可能会注意到这样一个事实:如果您遵循上面的链接,则注释中会有一些争论,即acquire/release语义是否真的适用于这种情况。您需要volatile和可能的锁定 volatile告诉优化程序值可以异步更改,因此
volatile bool flag = false;
while (!flag) {
/*do something*/
}
将在每次运行时读取标志
#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();
}
// global
bool exit_now = false;
// in one thread
while (!exit_now) { do_stuff; }
// in another thread, or signal handler in this thread
exit_now = true;
// Optimizing compilers transform the loop into asm like this
if (!exit_now) { // check once before entering loop
while(1) do_stuff; // infinite loop
}