zha*_*fei 0 c++ multithreading clang visual-c++ c++11
声明:我使用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;
}
Run Code Online (Sandbox Code Playgroud)
编译并运行代码: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
Run Code Online (Sandbox Code Playgroud)
很明显,当使用-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时,bug就消失了.
你的程序中包含的数据种族上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};
Run Code Online (Sandbox Code Playgroud)
或者使用a同步对它们的访问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);
}
Run Code Online (Sandbox Code Playgroud)
编辑:我刚注意到在OP中提到了VS2010.VS2010不支持C++ 11原子,因此您必须使用该mutex解决方案或利用MSVC的非标准扩展,它提供volatile变量获取 - 释放语义:
volatile bool s_begin_init = false;
volatile bool s_init_done = false;
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
193 次 |
| 最近记录: |