为什么线程不能识别标志的变化?

Why does thread not recognize change of a flag?

我在 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

最令人惊讶的是,while 循环没有退出,即使在标志已设置为 3 之后也是如此。

似乎编译器 "optimizes" 标志并假定它不能从外部更改。

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

删除整个 while 条件后,它按预期工作。 我应该将结构或其字段更改为 volatile 吗?每个人都说,volatile 在我们这个时代没有用,不再需要了,除非在这种情况下,内存位置被外围设备更改...

在 C11 之前,这完全取决于平台,因为您观察到的效果取决于您的平台使用的内存模型。这与编译器优化不同,因为线程之间的同步点需要编译器插入屏障指令,而不是例如将某些东西设为常量。对于第 7.17.3 节的 C11,指定了不同的模型。所以你的值没有被静态优化,线程 A 只是从不读取线程 B 写的值,但仍然有它的本地值。

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

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

编译器通常必须确保当 a 的值为 1 时 x 的值为 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