C++ 高效地等待线程池中的所有任务完成
目前,我的线程池中有一个包含x工作者的程序。在主循环期间,y任务分配给工人完成,但在任务发出后,我必须等待所有任务完成,然后才能开始执行程序。我认为我目前的解决方案效率低下,必须有更好的方法等待所有任务完成,但我不确定如何进行C++ 高效地等待线程池中的所有任务完成,c++,multithreading,threadpool,wait,stdthread,C++,Multithreading,Threadpool,Wait,Stdthread,目前,我的线程池中有一个包含x工作者的程序。在主循环期间,y任务分配给工人完成,但在任务发出后,我必须等待所有任务完成,然后才能开始执行程序。我认为我目前的解决方案效率低下,必须有更好的方法等待所有任务完成,但我不确定如何进行 // called in main after all tasks are enqueued to // std::deque<std::function<void()>> tasks void ThreadPool::waitFinished(
// called in main after all tasks are enqueued to
// std::deque<std::function<void()>> tasks
void ThreadPool::waitFinished()
{
while(!tasks.empty()) //check if there are any tasks in queue waiting to be picked up
{
//do literally nothing
}
}
这样我就可以发送成百上千的粒子位置任务,让它们并行完成,等待它们完成,然后将新数据放入openGL位置缓冲区中这是一种尝试的方法。在同一互斥体上使用两个条件变量不适合轻松的人,除非您知道内部发生了什么。我不需要原子处理成员,只需要演示每次运行之间完成了多少项 本文中的示例workload函数生成一百万个随机int值,然后对它们进行排序(以某种方式加热我的办公室)
waitFinished
将不会返回,直到队列为空且没有线程处于忙碌状态
#include <iostream>
#include <deque>
#include <functional>
#include <thread>
#include <condition_variable>
#include <mutex>
#include <random>
//thread pool
class ThreadPool
{
public:
ThreadPool(unsigned int n = std::thread::hardware_concurrency());
template<class F> void enqueue(F&& f);
void waitFinished();
~ThreadPool();
unsigned int getProcessed() const { return processed; }
private:
std::vector< std::thread > workers;
std::deque< std::function<void()> > tasks;
std::mutex queue_mutex;
std::condition_variable cv_task;
std::condition_variable cv_finished;
std::atomic_uint processed;
unsigned int busy;
bool stop;
void thread_proc();
};
ThreadPool::ThreadPool(unsigned int n)
: busy()
, processed()
, stop()
{
for (unsigned int i=0; i<n; ++i)
workers.emplace_back(std::bind(&ThreadPool::thread_proc, this));
}
ThreadPool::~ThreadPool()
{
// set stop-condition
std::unique_lock<std::mutex> latch(queue_mutex);
stop = true;
cv_task.notify_all();
latch.unlock();
// all threads terminate, then we're done.
for (auto& t : workers)
t.join();
}
void ThreadPool::thread_proc()
{
while (true)
{
std::unique_lock<std::mutex> latch(queue_mutex);
cv_task.wait(latch, [this](){ return stop || !tasks.empty(); });
if (!tasks.empty())
{
// got work. set busy.
++busy;
// pull from queue
auto fn = tasks.front();
tasks.pop_front();
// release lock. run async
latch.unlock();
// run function outside context
fn();
++processed;
latch.lock();
--busy;
cv_finished.notify_one();
}
else if (stop)
{
break;
}
}
}
// generic function push
template<class F>
void ThreadPool::enqueue(F&& f)
{
std::unique_lock<std::mutex> lock(queue_mutex);
tasks.emplace_back(std::forward<F>(f));
cv_task.notify_one();
}
// waits until the queue is empty.
void ThreadPool::waitFinished()
{
std::unique_lock<std::mutex> lock(queue_mutex);
cv_finished.wait(lock, [this](){ return tasks.empty() && (busy == 0); });
}
// a cpu-busy task.
void work_proc()
{
std::random_device rd;
std::mt19937 rng(rd());
// build a vector of random numbers
std::vector<int> data;
data.reserve(100000);
std::generate_n(std::back_inserter(data), data.capacity(), [&](){ return rng(); });
std::sort(data.begin(), data.end(), std::greater<int>());
}
int main()
{
ThreadPool tp;
// run five batches of 100 items
for (int x=0; x<5; ++x)
{
// queue 100 work tasks
for (int i=0; i<100; ++i)
tp.enqueue(work_proc);
tp.waitFinished();
std::cout << tp.getProcessed() << '\n';
}
// destructor will close down thread pool
return EXIT_SUCCESS;
}
祝你好运。你可以使用and。@WilliamAndrewMontgomery我尝试过这样做,但我需要一个互斥,不是吗?这将锁定停止所有其他线程的任务在windows中,您可以使用CreateThread()返回的句柄数组,然后对该数组使用WaitForMultipleObjects()等待所有线程终止(作为单个原子操作)。我想知道在STL中是否有与此等效的工具。或者更好的方法是,保留一个包含所有工作线程的
std::vector
,每个工作线程都在waitFinished
中。确实如此。关于Worker的用途还不清楚,但是调用函数对象队列背后的想法肯定已经足够清楚了。这应该是直截了当的。除非有人比我先到,否则我会看看能为你做些什么。重要的是要记住cv+mtx的目的。他们不是为了保持状态;它们用于保护谓词数据(即真实状态)并向其中的数据发送更改信号。无论如何,给我几分钟。我只是好奇,我能把一个lamba函数传递到队列中吗?我的函数需要3-4个变量。谢谢ton@SyntacticFructose当然,只要它符合void()
。否则,将参数打包并解包到一个具有延迟分派功能的实体中会变得复杂(但仍然可行)。改变工作进程可能最好使用一个简单的对象,它公开操作符()
(我想这就是你的Worker
类的全部内容,现在我想起来了)。嗯,我的程序挂起了,我相信这是因为我的lamba是[&](){/…]并且使用了循环中的一对变量。这可能是原因吗?看起来你的代码很有效,所以又是我的实现。我把它设为赏金+50,你帮了很多忙,因为busy
在不受queue\u mutex
保护的情况下减少了busy
:当busy
的此修改和对notify\u one()
的以下调用在waitFinished()
中的lambda条件计算完成后立即发生(即到false
)并且就在实际等待开始之前,通知将丢失,waitFinished()的调用方
将永远等待。我认为这就是@SyntacticFructose遇到挂起程序的原因。也许你可以相应地编辑你本来很好的答案。@ph4nt0m你完全正确。我将不得不在成员mods之前对其进行修改和锁定。感谢你关注这一点,并感谢所有为你的评论打勾的人(我加入了他们=P)。
while(running)
//....
for all particles alive
push particle position function to threadpool
end for
threadPool.waitFinished();
push new particle position data into openGL buffer
end while
#include <iostream>
#include <deque>
#include <functional>
#include <thread>
#include <condition_variable>
#include <mutex>
#include <random>
//thread pool
class ThreadPool
{
public:
ThreadPool(unsigned int n = std::thread::hardware_concurrency());
template<class F> void enqueue(F&& f);
void waitFinished();
~ThreadPool();
unsigned int getProcessed() const { return processed; }
private:
std::vector< std::thread > workers;
std::deque< std::function<void()> > tasks;
std::mutex queue_mutex;
std::condition_variable cv_task;
std::condition_variable cv_finished;
std::atomic_uint processed;
unsigned int busy;
bool stop;
void thread_proc();
};
ThreadPool::ThreadPool(unsigned int n)
: busy()
, processed()
, stop()
{
for (unsigned int i=0; i<n; ++i)
workers.emplace_back(std::bind(&ThreadPool::thread_proc, this));
}
ThreadPool::~ThreadPool()
{
// set stop-condition
std::unique_lock<std::mutex> latch(queue_mutex);
stop = true;
cv_task.notify_all();
latch.unlock();
// all threads terminate, then we're done.
for (auto& t : workers)
t.join();
}
void ThreadPool::thread_proc()
{
while (true)
{
std::unique_lock<std::mutex> latch(queue_mutex);
cv_task.wait(latch, [this](){ return stop || !tasks.empty(); });
if (!tasks.empty())
{
// got work. set busy.
++busy;
// pull from queue
auto fn = tasks.front();
tasks.pop_front();
// release lock. run async
latch.unlock();
// run function outside context
fn();
++processed;
latch.lock();
--busy;
cv_finished.notify_one();
}
else if (stop)
{
break;
}
}
}
// generic function push
template<class F>
void ThreadPool::enqueue(F&& f)
{
std::unique_lock<std::mutex> lock(queue_mutex);
tasks.emplace_back(std::forward<F>(f));
cv_task.notify_one();
}
// waits until the queue is empty.
void ThreadPool::waitFinished()
{
std::unique_lock<std::mutex> lock(queue_mutex);
cv_finished.wait(lock, [this](){ return tasks.empty() && (busy == 0); });
}
// a cpu-busy task.
void work_proc()
{
std::random_device rd;
std::mt19937 rng(rd());
// build a vector of random numbers
std::vector<int> data;
data.reserve(100000);
std::generate_n(std::back_inserter(data), data.capacity(), [&](){ return rng(); });
std::sort(data.begin(), data.end(), std::greater<int>());
}
int main()
{
ThreadPool tp;
// run five batches of 100 items
for (int x=0; x<5; ++x)
{
// queue 100 work tasks
for (int i=0; i<100; ++i)
tp.enqueue(work_proc);
tp.waitFinished();
std::cout << tp.getProcessed() << '\n';
}
// destructor will close down thread pool
return EXIT_SUCCESS;
}
100
200
300
400
500