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

时间:2014-04-23 01:47:27

标签: c++ multithreading visual-c++ c++11 clang

声明:我使用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时,错误消失了。

1 个答案:

答案 0 :(得分: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};

或使用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;