为什么线程无法识别标志的更改?

时间:2018-07-09 09:02:59

标签: c multithreading compiler-optimization volatile

在Windows 7平台上的C / Visual Studio下,我遇到一个奇怪的情况。有时会出现问题,我花了很多时间来找到它。问题出在第三方库中,对此我拥有完整的代码。在那里创建了一个线程(printLog语句来自我自己):

    ...
    plafParams->eventThreadFlag = 2;
    printLog("before CreateThread");
    if (plafParams->hReadThread_p = CreateThread(NULL, 0, ( LPTHREAD_START_ROUTINE ) plafPortReadThread, ( void * ) dlmsInstance, 0,
                                                 &plafParams->portReadThreadID) )
    {
        printLog("after CreateThread: OK");
        plafParams->eventThreadFlag = 3;
    }
    else
    {
        unsigned int lasterr = GetLastError();
        printLog("error CreateThread, last error:%x", lasterr);
        /* Could not create the read thread. */
        ...
        ...
        return FAILURE;
    }

    printLog("SUCCESS");
    ...
    ...

线程函数是:

    void *plafPortReadThread(DLMS_GLOBALS *dlmsInstance)
    {

        PLAF_PARAMS *plafParams;


        plafParams = (PLAF_PARAMS *)(dlmsInstance->plafParams);
        printLog("start - plafPortReadThread, plafParams->eventThreadFlag=%x", plafParams->eventThreadFlag);

        while ((plafParams->eventThreadFlag != 1) && (plafParams->eventThreadFlag != 3))
        {
            if (plafParams->eventThreadFlag == 0)
            {
                printLog("exit 1 - plafPortReadThread, plafParams->eventThreadFlag=%x", plafParams->eventThreadFlag);
                CloseHandle(plafParams->hReadThread_p);
                plafFree((void **)&plafParams);
                ExitThread(0);
               break;
            }
        }
        printLog("start - plafPortReadThread, proceed=%d", proceed);
        ...

现在,当在线程内启动while循环之前设置了标志时,一切正常:

SUCCESS
start - plafPortReadThread, plafParams->eventThreadFlag=3

但是有时候线程足够快,因此在实际在外部设置标志之前就启动了while循环。

则输出为:

start - plafPortReadThread, plafParams->eventThreadFlag=2
SUCCESS

最令人惊讶的是,即使将标志设置为3,while循环也不会退出。

似乎,编译器“优化”了该标志并假定它不能从外部更改。

可能是什么问题?我真的很惊讶还是我完全监督了其他事情?我知道,代码不是很优雅,最好用信号量或信号来完成这些事情。但这不是我的代码,我希望更改尽可能少。

除去所有while条件后,它会按预期工作。 我应该将结构或其字段更改为volatile吗?所有人都说volatile在我们这个时代是没有用的,不再需要了,除非外围设备改变了存储位置...

1 个答案:

答案 0 :(得分:2)

在C11之前,这完全依赖于平台,因为您观察到的效果是由于平台所使用的内存模型所致。这与编译器优化不同,因为线程之间的同步点要求编译器插入屏障指令,而不是例如使某些常量成为常数。对于C11,第7.17.3节指定不同的模型。因此,您的值不会被静态优化,线程A永远不会读取线程B写入的值,但仍具有其本地值。

实际上,许多项目还没有使用C11,因此您可能必须检查平台的文档。请注意,在许多情况下,您不必修改标志的变量类型(以防万一)。大多数内存模型都指定了同步点,这些同步点还禁止对某些指令进行重新排序,例如:

int x = 3;
_Atomic int a = 1;
x = 5;
a = 2;

x的值为1时,编译器通常必须确保a的值为3,并且为a分配的值为2,x将具有值5。volatile不参与这种关系(在C / C ++ 11模型中-经常会混淆,因为它确实参与了Java的事前活动),并且几乎没有用,除非您的写信永远不会进行了优化,因为它们具有副作用,例如LED闪烁,编译器无法理解:

volatile int x = 1; // some special location - blink then clear 
x = 1; // blink then clear
x = 1; // blink then clear