【问题标题】:Why does thread not recognize change of a flag?为什么线程无法识别标志的变化?
【发布时间】:2018-07-09 09:02:58
【问题描述】:

我在 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 之后也是如此。

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

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

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

【问题讨论】:

  • 行为观察确实是一种优化。 volatile 不是没用的,这可能就是你需要的。但可能这还不够,您需要锁/原子标志来确保正确访问共享数据。
  • 我不知道 - 编译器肯定不应该完全优化它吗?如果这确实是原因,Volatile 应该对其进行分类,但我们不应该再猜测编译器忽略了多少我们的代码......另外,为什么不记录帖子创建线程记录消息?
  • 从技术上讲,我相信它需要_Atomic 而不是volatile。没有_Atomic 它应该是UB,但我仍然认为它应该在通用平台(x86)上工作。
  • volatile 在一般情况下对您没有帮助(例如,在处理在不同内核上运行的线程时)。您需要的是memory barrier。通常使用互斥锁或信号量。
  • 最有趣的是,即使在答案中也没有关于“易失性”的同质观点。现在是不是没用了?

标签: c multithreading compiler-optimization volatile


【解决方案1】:

在 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。@987654326 @ 不参与这种关系(在 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

【讨论】:

    猜你喜欢
    • 2016-06-08
    • 2018-10-21
    • 2018-07-17
    • 2020-07-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多