声明:我使用vs 2010 / vs 2013,并使用3.4预建二进制文件。
我在生产代码中发现了一个错误。我将重现代码最小化到以下内容:
#include <windows.h>
#include <process.h>
#include <stdio.h>
using namespace std;
bool s_begin_init = false;
bool s_init_done = false;
void thread_proc(void * arg)
{
DWORD tid = GetCurrentThreadId();
printf("Begin Thread %2d, TID=%u\n", reinterpret_cast<int>(arg), tid);
if (!s_begin_init)
{
s_begin_init = true;
Sleep(20);
s_init_done = true;
}
else
{
while(!s_init_done) { ; }
}
printf("End Thread %2d, TID=%u\n", reinterpret_cast<int>(arg), tid);
}
int main(int argc, char *argv[])
{
argc = argc ; argv = argv ;
for(int i = 0; i < 30; ++i)
{
_beginthread(thread_proc, 0, reinterpret_cast<void*>(i));
}
getchar();
return 0;
}
编译并运行代码: cl / O2 / Zi /Favc.asm vc_O2_bug.cpp&amp;&amp; vc_O2_bug.exe
一些线程正在while循环中忙。通过检查生成的汇编代码,我找到了
的汇编代码while(!s_init_done){; }
是:
; Line 19
mov al, BYTE PTR ?s_init_done@@3_NA ; s_init_done
$LL2@thread_pro:
; Line 21
test al, al
je SHORT $LL2@thread_pro
; Line 23
很明显,当使用-O2优化标志时,VC将s_init_done复制到al寄存器,并反复测试al寄存器。
然后我使用clang-cl.exe编译器驱动程序来测试代码。结果相同,汇编代码为
等效。
看起来编译器认为变量s_init_done永远不会被更改,因为唯一更改它的值的语句位于“if”块中,该块与当前“else”分支不同。
我在VS2013上尝试了相同的代码,结果也一样。
我怀疑的是:在C ++ 98 / C ++ 03标准中,没有线程的概念。因此编译器可以为单线程机器执行这样的优化。但由于c ++ 11有线程,并且clang 3.4和VC2013都支持C ++ 11,我的问题是:
认为优化是C ++ 98 / C ++ 03和C ++ 11的编译器错误吗?
BTW:当我使用-O1代替,或者将volatile限定符添加到s_init_done时,错误消失了。
答案 0 :(得分:7)
您的程序包含s_begin_init
和s_init_done
上的数据争用,因此具有未定义的行为。 Per C ++11§1.10/ 21:
程序的执行包含数据竞争,如果它在不同的线程中包含两个冲突的动作,其中至少有一个不是原子的,并且在另一个之前都不会发生。任何此类数据竞争都会导致未定义的行为。
修复是将两个布尔变量声明为原子:
std::atomic<bool> s_begin_init{false};
std::atomic<bool> s_init_done{false};
或使用mutex
同步对它们的访问(我会抛出一个条件变量以避免忙等待):
std::mutex mtx;
std::condition_variable cvar;
bool s_begin_init = false;
bool s_init_done = false;
void thread_proc(void * arg)
{
DWORD tid = GetCurrentThreadId();
printf("Begin Thread %2d, TID=%u\n", reinterpret_cast<int>(arg), tid);
std::unique_lock<std::mutex> lock(mtx);
if (!s_begin_init)
{
s_begin_init = true;
lock.unlock();
Sleep(20);
lock.lock();
s_init_done = true;
cvar.notify_all();
}
else
{
while(!s_init_done) { cvar.wait(lock); }
}
printf("End Thread %2d, TID=%u\n", reinterpret_cast<int>(arg), tid);
}
编辑:我刚注意到在OP中提到了VS2010。 VS2010 does not support C++11 atomics,因此您必须使用mutex
解决方案或利用MSVC's non-standard extension that gives volatile
variables acquire-release semantics:
volatile bool s_begin_init = false;
volatile bool s_init_done = false;