首页 > 解决方案 > 互斥量和变量更新

问题描述

需要验证一个思维过程,比如说我设置了一个线程如下

bool threadRun=true;
std::mutex threadMutex;
std::condition_variable event;
std::thread processThread(process);

void process()
{
    while(threadRun)
    {
        if(noWork())
        {
            std::unique_lock<std::mutex> lock(threadMutex);

            if(threadRun)
            {
                if(!getWork()) //try to get work, return false if no work
                    event.wait(lock);
            }

            continue;
        }

        doWork();
    }
}

void stopThread()
{
    {
        std::unique_lock<std::mutex> lock(threadMutex);
        threadRun=false;
    }
    event.notify_all();
    processThread.join();
}

由于 threadRun 不在线程中的互斥锁下,因此无法保证调用 stopThread 会真正停止线程,因为当有待办事项时不需要缓存刷新。但是,当没有工作并且锁定被占用时,这应该会导致缓存刷新和 threadRun 保证被更新,对吗?

标签: c++multithreadingc++11thread-safety

解决方案


C++ 中的数据竞争是未定义的行为。

现在,您可能会认为这意味着“好吧,我可以接受他们错过阅读,他们最终会明白的”。但是 UB 是编译器可以自由假设不会发生的事情。

您在没有锁或其他同步的情况下阅读的事实threadRun意味着编译器可以、可能并且应该假设任何其他线程中的任何人都不会修改threadRun.

因此,代码可以优化为:

if(threadRun)
  while(true)

即,编译器可以读取threadRun一次,然后如果它可以证明没有人可以以定义的方式更改其值,则无需再阅读它。

现在,如果编译器无法证明这一点,它就必须读取threadRun. 所以你的 UB 表现得更像它“应该”工作。在某些平台上,硬件本身已缓存threadRun在每个处理器(或线程)缓存中,并且另一个线程写入它的事实不会在某个未知时间段(可能永远)内反映在您的工作线程中。

更进一步,智能编译器可能会注意到您正在阅读threadRun而不同步。然后它可以用它来证明没有人,无论有没有同步,都可以threadRun在另一个线程中写入。

一旦证明了这一点,它也可以消除threadRun互斥体内部的检查。

不要做未定义的行为,除非您想在每次编译时验证程序从现在到结束时生成的程序集,即使它“有效”,或者收益巨大且值得冒险。

替换threadRunstd::atomic_bool。在最坏的情况下,您会损失一点点性能并获得正确性。


推荐阅读