这种优化是否是编译器错误?

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就消失了.

Cas*_*sey 7

你的程序中包含的数据种族上s_begin_inits_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)