【发布时间】:2014-06-07 15:25:22
【问题描述】:
声明:我使用 vs 2010/vs 2013 和 clang 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 && 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 的编译器错误吗?
顺便说一句:当我改用 -O1 或将 volatile 限定符添加到 s_init_done 时,错误消失了。
【问题讨论】:
-
这不是编译器错误,您的程序中没有任何内容会强制编译器不根据标准优化静态变量负载。
-
@CaptainObvlious:你确定按照标准就够了吗?
-
@Deduplicator MSVC 为
volatile提供获取-释放语义;这是一个非标准的扩展。数据竞争的标准解决方法是将s_begin_init和s_init_done变成std::atomic<bool>。 -
@Deduplicator To force a reload yes but it doesn't address the race condition.
-
从技术上讲,您的
printf格式字符串中还有未定义的行为,DWORD是typedefed 为unsigned long并且需要%lu。
标签: c++ multithreading visual-c++ c++11 clang