C++ 条件变量不';即使有谓词,也不会被通知醒来

C++ 条件变量不';即使有谓词,也不会被通知醒来,c++,multithreading,C++,Multithreading,我遇到了一个问题,一些条件变量陷入了等待阶段,即使他们已经得到通知。每一个都有一个谓词,以防它们错过来自主线程的notify调用 代码如下: unsigned int notifyCount = 10000; std::atomic<int> threadCompletions = 0; for (unsigned int i = 0; i < notifyCount; i++) { std::atomic<bool>* wakeUp = new std:

我遇到了一个问题,一些
条件变量
陷入了
等待
阶段,即使他们已经得到通知。每一个都有一个谓词,以防它们错过来自主线程的notify调用

代码如下:

unsigned int notifyCount = 10000;
std::atomic<int> threadCompletions = 0;

for (unsigned int i = 0; i < notifyCount; i++)
{
    std::atomic<bool>* wakeUp = new std::atomic<bool>(false);
    std::condition_variable* condition = new std::condition_variable();

    // Worker thread //
    std::thread([&, condition, wakeUp]()
    {
        std::mutex mutex;
        std::unique_lock<std::mutex> lock(mutex);
        condition->wait(lock, [wakeUp] { return wakeUp->load(); });

        threadCompletions++;
    }).detach();

    // Notify //
    *wakeUp = true;
    condition->notify_one();
}

Sleep(5000); // Sleep for 5 seconds just in case some threads are taking a while to finish executing

// Check how many threads finished (threadCompletions should be equal to notifyCount)
unsigned int notifyCount=10000;
std::原子线程完成=0;
for(无符号整数i=0;i等待(锁定,[唤醒]{返回唤醒->加载();});
threadCompletions++;
}).detach();
//通知//
*唤醒=真;
条件->通知一个();
}
睡眠(5000);//睡眠5秒钟,以防某些线程需要一段时间才能完成执行
//检查已完成的线程数(threadCompletions应等于notifyCount)
除非我弄错了,否则在for循环完成后,
threadCompletions
应该始终等于
notifyCount
。但很多时候,情况并非如此

在发行版中运行时,我有时只会得到10000个从未完成的线程中的一个或两个,但在调试中运行时,我会得到20个或更多

我认为线程中的
wait
调用可能是在主线程的
notify\u one
调用之后发生的(这意味着它错过了唤醒的通知),因此我向
wait
传递了一个谓词,以确保它不会陷入等待状态。但在某些情况下仍然如此


有人知道为什么会发生这种情况吗?

您假设对
wait()
的调用是原子的。我不相信是这样。这就是为什么它需要使用互斥锁和锁

考虑以下几点:

Main Thread.                        Child Thread

                                    // This is your wait unrolled.
                                    while (!wakeUp->load()) {    


// This is atomic
// But already checked in the
// thread.
*wakeUp = true;

// Child has not yet called wait
// So this notify_one is wasted.
condition->notify_one();


                                        // The previous call to notify_one
                                        // is not recorded and thus the
                                        // thread is now locked in this wait
                                        // never to be let free.
                                        wait(lock);
                                    }

// Your race condition.
notify_one()
wait()
的调用应通过相同的mutext进行控制,以确保它们不会像这样重叠

for (unsigned int i = 0; i < notifyCount; i++)
{

    std::atomic<bool>* wakeUp = new std::atomic<bool>(false);
    std::mutex*              mutex     = new std::mutex{};
    std::condition_variable* condition = new std::condition_variable();

    // Worker thread //
    std::thread([&]()
    {
        std::unique_lock<std::mutex> lock(*mutex);
        condition->wait(lock, [&wakeUp] { return wakeUp->load(); });

        threadCompletions++;
    }).detach();

    // Notify //
    *wakeUp = true;

    std::unique_lock<std::mutex> lock(*mutex);
    condition->notify_one();
}

// Don't forget to clean up the new structures correctly/.
for(无符号整数i=0;i等待(锁定,[&wakeUp]{return wakeUp->load();});
threadCompletions++;
}).detach();
//通知//
*唤醒=真;
std::唯一锁(*互斥锁);
条件->通知一个();
}
//别忘了正确地清理新结构。

您有数据竞赛。考虑下面的场景:

  • 工作线程:条件变量测试唤醒是否为真-不是

  • 主线程:wakeup设置为true,条件变量得到通知

  • 工作线程:条件_变量触发等待,但它发生在通知已经发生之后-这意味着通知未命中,线程可能永远不会唤醒


通常情况下,条件变量的同步是通过互斥来完成的——原子在这里没有太大帮助。在C++20中,原子中会有一种特殊的等待/通知机制。

如果在
*wakeUp=true
之前放置一个
这个线程::sleep\u(5秒)
会发生什么?你创建了10000个线程!在您的机器上创建超过内核数量的内核可能会适得其反(因此4通常是一个好数字(如果您有一台好的机器,则为8))。创建由线程执行的“工作对象”(查找线程池(或
async()
,如果可以使用C++17)@MartinYork@Fureeish答案是否与我的答案不一致?这是否意味着你可以让10000个线程高效运行。此外,线程重量重,创建成本高。当然,你可以获得比内核数量更多的好处(但要确定这种平衡并不简单)。因此,对于初学者来说,最好的经验法则是不超过内核数。您没有在两个线程中锁定相同的互斥体。互斥体不起任何作用。那么,这里的解决方案是什么?如何确保工作线程在我通知之前等待,或者在条件_变量执行其谓词条件之前,唤醒设置为truen检查?如果
*wakeUp
为真,则为否实际
等待(互斥)
不是简单地调用
条件->等待(锁定,[wakeUp]{return wakeUp->load();});
立即返回,因为在您的场景中,
wakeUp
true
@nullptr不会使
wakeUp
原子化。而是通过条件变量使用的同一互斥锁来保护其修改。因此,您必须在线程之外创建它。@MarekR我想他是说,在wakeUp时完成的谓词检查已经发生了在主线程将其设置为true之前调用,然后实际的wait()调用在主线程调用notify_one之后发生。@nullptr使用互斥体,lukemutex应声明为
std::mutex*