我正在优化一个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
)。由于我不是一个优化标志专家,任何人都可以告诉我他们中哪一个对此结果负责,所以我可以将其关闭?关闭它会对其他代码有任何重大的性能成本吗?我的意思是,这种代码模式很少见?
答案 0 :(得分:6)
此代码具有未定义的行为。您正在从一个线程修改hit
并从另一个线程中读取它,而不进行同步。
优化hit
到false
是未定义行为的有效结果。您可以通过hit
一个std::atomic<bool>
来解决此问题。如果定义明确,则会阻止优化。
答案 1 :(得分:2)
如果你想同时从多个线程读/写hit
,那么你需要某种同步,否则你会引入竞争条件。您可以将hit
设为std::atomic<bool>
,也可以在访问mutex
值时添加需要锁定的hit
。如果你只是想等待线程完成它的工作,你可以只留下thread.join()
(并在它之后打印“完成”)而不引入任何额外的标志。
答案 2 :(得分:1)
通过将hit
声明为volatile,您可以告诉编译器此变量可以随时被外部因素修改,因此编译器不会认为其值已赢得&#39; t沿着main
功能改变。
只要有一个线程写入hit
变量,您的代码就可以正常工作,不会涉及竞争条件。但是,当您处理多个线程时,使用同步工具(如原子对象,互斥锁和信号量)总是更安全,如此处其他答案中所述。