【发布时间】:2017-10-27 17:55:37
【问题描述】:
我在优化一个c++代码时遇到了一种情况,可以简化如下。
考虑这段代码:
#include <iostream>
#include <thread>
using namespace std;
bool hit = false;
void F()
{
this_thread::sleep_for(chrono::seconds(1));
hit = true;
}
int main()
{
thread t(F);
while (!hit)
;
cout << "finished" << endl;
t.join();
return 0;
}
这基本上会启动一个线程,该线程会在一秒钟后将hit 的值更改为true。同时,代码进入一个空循环,该循环将一直持续到hit 的值变为true。我用gcc-5.4 使用-g 标志编译了这个,一切都很好。代码将输出finished 并结束。但是后来我用-O2标志编译它,这一次代码无限卡在循环中。
查看反汇编,编译器生成了以下内容,这就是死循环的根本原因:
jmp 0x6ba6f3 ! 0x00000000006ba6f3
好的,很明显,编译器已经推断出hit 的值是false,它不会在循环中改变,所以为什么不假设它是一个无限循环而不考虑另一个线程可能会改变它的值!并且这种优化模式是在更高级别添加的(-O2)。由于我不完全是优化标志专家,谁能告诉我他们中的哪一个对这个结果负责,所以我可以把它关掉?并且关闭它会对其他代码段产生任何重大的性能成本吗?我的意思是,这种代码模式有多罕见?
【问题讨论】:
-
使用
std::atomic<bool>。 -
如果将
hit声明为易失性会发生什么? -
@Sinapse:不,实际上,它没有。它只会让症状消失 - 现在。麻烦的是,它仍然是未定义的行为,如果您更改一行代码,优化器可能会做出一些新的决定。
-
@MSalters 我大致理解了这个问题, volatile 解决的只是编译器的优化!但是由于 bool(1 字节)在读/写时不会面临撕裂,而
cache coherence在上面的示例中并不重要(它只会导致循环超出实际需要的部分),我们很高兴在这种情况下进行当然 -
@Sinapse:标准在这一点上非常明确。这不是“撕裂”或“缓存一致性”,标准没有谈到这一点。对像这样必须这样的单个变量的两次访问是有序的,以未定义行为为代价。
volatile不会影响排序,也不会治愈未定义的行为。就标准而言,每个线程都可以获得自己的volatile bool hit!
标签: c++ multithreading gcc optimization