C++ std::condition_variable::notify_all()只唤醒线程池中的一个线程

C++ std::condition_variable::notify_all()只唤醒线程池中的一个线程,c++,threadpool,std,condition-variable,C++,Threadpool,Std,Condition Variable,我想写一个简单的线程池来学习它们是如何在引擎盖下工作的。不过,我遇到了一个问题。当我使用条件变量并调用notify_all()时,它只唤醒池中的一个线程 其他一切都很好。我已经排了900个工作,每个工作都有一个不错的负载。一个醒来的线程消耗了所有这些作业,然后又回到睡眠状态。在下一个循环中,这一切再次发生 问题是,只有一个线程可以工作!我是怎么搞砸这个模板的 ThreadPool.h: #pragma once #include <mutex> #include <stack

我想写一个简单的线程池来学习它们是如何在引擎盖下工作的。不过,我遇到了一个问题。当我使用条件变量并调用notify_all()时,它只唤醒池中的一个线程

其他一切都很好。我已经排了900个工作,每个工作都有一个不错的负载。一个醒来的线程消耗了所有这些作业,然后又回到睡眠状态。在下一个循环中,这一切再次发生

问题是,只有一个线程可以工作!我是怎么搞砸这个模板的

ThreadPool.h:

#pragma once

#include <mutex>
#include <stack>
#include <atomic>
#include <thread>
#include <condition_variable>

class ThreadPool
{
friend void __stdcall ThreadFunc();

public:
    static ThreadPool & GetInstance()
    {
        static ThreadPool sInstance;

        return (sInstance);
    }

public:
    void AddJob(Job * job);
    void DoAllJobs();

private:
    Job * GetJob();

private:
    const static uint32_t ThreadCount = 8;

    std::mutex                  JobMutex;
    std::stack<Job *>           Jobs;
    volatile std::atomic<int>   JobWorkCounter;
    std::mutex                  SharedLock;
    std::thread                 Threads[ThreadCount];
    std::condition_variable     Signal;

private:
    ThreadPool();
    ~ThreadPool();

public:
    ThreadPool(ThreadPool const &) = delete;
    void operator = (ThreadPool const &) = delete;
};
#pragma一次
#包括
#包括
#包括
#包括
#包括
类线程池
{
friend void_uustdcall ThreadFunc();
公众:
静态线程池&GetInstance()
{
静态线程池;
返回(sInstance);
}
公众:
void AddJob(Job*Job);
void DoAllJobs();
私人:
Job*GetJob();
私人:
const static uint32_t ThreadCount=8;
std::mutex job mutex;
std::堆栈作业;
挥发性标准::原子作业计数器;
std::互斥共享锁;
标准::线程数[线程数];
std::条件变量信号;
私人:
线程池();
~ThreadPool();
公众:
线程池(线程池常量&)=删除;
void运算符=(线程池常量&)=删除;
};
ThreadPool.cpp:

#include "ThreadPool.h"

void __stdcall ThreadFunc()
{
    std::unique_lock<std::mutex> lock(ThreadPool::GetInstance().SharedLock);

    while (true)
    {
        ThreadPool::GetInstance().Signal.wait(lock);

        while (Job * job = ThreadPool::GetInstance().GetJob())
        {
            job->_jobFn(job->_args);
            ThreadPool::GetInstance().JobWorkCounter--;
        }
    }
}

ThreadPool::ThreadPool()
{
    JobWorkCounter = 0;

    for (uint32_t i = 0; i < ThreadCount; ++i)
        Threads[i] = std::thread(ThreadFunc);
}

ThreadPool::~ThreadPool()
{
}

void ThreadPool::AddJob(Job * job)
{
    JobWorkCounter++;

    JobMutex.lock();
    {
        Jobs.push(job);
    }
    JobMutex.unlock();
}

void ThreadPool::DoAllJobs()
{
    Signal.notify_all();

    while (JobWorkCounter > 0)
    {
        Sleep(0);
    }
}

Job * ThreadPool::GetJob()
{
    Job * return_value = nullptr;

    JobMutex.lock();
    {
        if (Jobs.empty() == false)
        {
            return_value = Jobs.top();
            Jobs.pop();
        }
    }
    JobMutex.unlock();

    return (return_value);
}
#包括“ThreadPool.h”
void uu stdcall ThreadFunc()
{
std::unique_lock锁(ThreadPool::GetInstance().SharedLock);
while(true)
{
线程池::GetInstance().Signal.wait(锁);
while(Job*Job=ThreadPool::GetInstance().GetJob())
{
作业->作业fn(作业->参数);
ThreadPool::GetInstance().JobWorkCounter--;
}
}
}
线程池::线程池()
{
作业计数器=0;
对于(uint32_t i=0;i0)
{
睡眠(0);
}
}
作业*线程池::GetJob()
{
作业*返回值=nullptr;
JobMutex.lock();
{
if(Jobs.empty()==false)
{
return_value=Jobs.top();
Jobs.pop();
}
}
JobMutex.unlock();
返回值(返回值);
}
谢谢你的帮助!抱歉发了这么大的代码帖子

std::unique_lock<std::mutex> lock(ThreadPool::GetInstance().SharedLock);
当主线程执行
notify_All()
时,所有线程都将收到来自条件变量的信号,但您忘记了一个关键细节:在收到条件变量通知后醒来后,互斥锁将自动重新锁定。这就是
wait()
的工作原理。在C++书籍或手册页中阅读它的文档;只有一个线程能够做到这一点。所有其他醒来的线程也将尝试锁定互斥锁,但只有第一个线程赢得比赛并将这样做,而所有其他线程将睡眠并继续做梦

收到通知后的线程将
wait()
返回,直到该线程也成功地重新锁定互斥锁

要从
wait()
返回,必须发生两件事:线程从条件变量得到通知,并且线程成功地重新锁定互斥锁
wait()
解锁互斥锁并原子地等待条件变量,并在收到通知时重新锁定互斥锁

因此,幸运线程将锁定互斥锁,并继续清空所有作业的队列,然后返回循环顶部并再次
wait()
。这将解锁互斥锁,现在其他一些幸运的线程,已经被通知,但耐心等待它的机会沐浴在阳光和荣耀中,将能够锁定互斥锁。以这种方式,所有其他线程将轮流,象式,醒来,检查作业队列,在那里找不到任何东西,然后进入睡眠

这就是你看到这种行为的原因

要使显示的代码线程安全,必须做两件基本的事情

1) 您不需要两个互斥体,一个就足够了

2) 在对条件变量执行
wait()
ing操作之前,请检查作业队列中是否有内容。如果有问题,请将其删除,并解锁互斥锁,然后执行此操作

3)
wait()
仅当作业队列为空时。在
wait()
返回后,重新锁定互斥锁,然后检查作业队列是否仍然为空(此时不能真正保证它不是空的,只能保证它可能是非空的)

您只需要一个互斥来保护对非线程安全作业队列的访问,并等待条件变量

当主线程执行
notify_All()
时,所有线程都将收到来自条件变量的信号,但您忘记了一个关键细节:在收到条件变量通知后醒来后,互斥锁将自动重新锁定。这就是
wait()
的工作原理。在C++书籍或手册页中阅读它的文档;只有一个线程能够做到这一点。所有其他醒来的线程也将尝试锁定互斥锁,但只有第一个线程赢得比赛并将这样做,而所有其他线程将睡眠并继续做梦

收到通知后的线程将
wait()
返回,直到该线程也成功地重新锁定互斥锁

要从
wait()
返回,必须发生两件事:线程从条件变量得到通知,并且线程成功地重新锁定互斥锁<代码>等待()解锁mu
ThreadPool::GetInstance().Signal.wait(lock);
std::condition_variable cv;
mutable std::mutex m;
your_message_type message;
std::unique_lock l{m}; // C++17, don't need to pass type
set_message_data(message);
cv.notify_one();
std::unique_lock l{m};
set_lots_of_message_data(message);
cv.notify_all();
while(true) {
  auto data = [&]()->std::optional<data_to_process>{
    std::unique_lock l{m};
    cv.wait( l, [&]{ return done() || there_is_a_message(message); } );
    if (done()) return {};
    return get_data_to_process(message);
  }();
  if (!data) break;
  auto& data_to_process = *data;
  // process the data
}