#ifndef THREAD_POOL_H
#define THREAD_POOL_H
#include <vector>
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>
class ThreadPool {
public:
ThreadPool(size_t);
template<class F, class... Args>
auto enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type>;
~ThreadPool();
private:
// need to keep track of threads so we can join them
std::vector< std::thread > workers;
// the task queue
std::queue< std::function<void()> > tasks;
// synchronization
std::mutex queue_mutex;
std::condition_variable condition;
bool stop;
};
// the constructor just launches some amount of workers
inline ThreadPool::ThreadPool(size_t threads)
: stop(false)
{
for(size_t i = 0;i<threads;++i)
workers.emplace_back(
[this]
{
for(;;)
{
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(this->queue_mutex);
this->condition.wait(lock,
[this]{ return this->stop || !this->tasks.empty(); });
if(this->stop && this->tasks.empty())
return;
task = std::move(this->tasks.front());
this->tasks.pop();
}
task();
}
}
);
}
// add new work item to the pool
template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type>
{
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared< std::packaged_task<return_type()> >(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex);
// don't allow enqueueing after stopping the pool
if(stop)
throw std::runtime_error("enqueue on stopped ThreadPool");
tasks.emplace([task](){ (*task)(); });
}
condition.notify_one();
return res;
}
// the destructor joins all threads
inline ThreadPool::~ThreadPool()
{
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all();
for(std::thread &worker: workers)
worker.join();
}
#endif
enqueue
enqueue函数定义
该函数的目的是将任务添加到线程池的任务队列中,并返回一个 std::future 对象,以便调用者可以在任务完成后获取其结果。
具体来说,enqueue 函数是一个模板函数,接受一个可调用对象 f 和一组参数 args。这些参数使用了完美转发(perfect forwarding),即通过 F&& 和 Args&&... 来实现。这种方式确保了参数的原始类型和值类别(左值或右值)得以保留。
函数的返回类型是 std::future<typename std::result_of<F(Args...)>::type>。这里使用了 std::result_of 元编程工具来推断可调用对象 f 使用参数 args... 调用后的返回类型。std::future 是一个标准库类模板,用于表示异步操作的结果。通过返回 std::future 对象,调用者可以在稍后某个时间点等待任务完成并获取其结果。
enqueue函数实现
首先,函数模板参数 F 和 Args 分别表示任务函数类型和其参数类型。函数返回类型是一个 std::future 对象,类型为任务函数的返回类型。通过 std::result_of<F(Args...)>::type 获取任务函数的返回类型,并将其定义为 return_type。
接下来,使用 std::make_shared 创建一个 std::packaged_task 对象,并将任务函数和参数绑定到一起。std::packaged_task 是一个包装器,它将任务函数封装起来,使其可以异步执行。std::bind 用于将任务函数和参数绑定在一起,而 std::forward 则确保参数的完美转发。
然后,通过 task->get_future() 获取一个 std::future 对象 res,用于稍后获取任务的结果。接着,使用 std::unique_lock 锁住互斥量 queue_mutex,以确保线程安全地访问任务队列。
在锁的保护下,首先检查线程池是否已经停止,如果是,则抛出一个运行时错误。否则,将任务添加到任务队列中。任务被封装在一个 lambda 表达式中,该表达式在执行时会调用 task 对象。
最后,使用 condition.notify_one() 唤醒一个等待的线程,以便它可以从任务队列中取出并执行任务。函数返回 std::future 对象 res,调用者可以通过它获取任务的执行结果。
析构函数
首先,使用 std::unique_lock 锁住互斥量 queue_mutex,并将 stop 标志设置为 true,表示线程池即将停止,不再接受新的任务。
接下来,调用 condition.notify_all(),唤醒所有等待条件变量的线程。这些线程会检查 stop 标志,并终止它们的执行。
最后,遍历 workers 容器中的每一个线程对象,并调用 join() 方法,确保主线程等待所有工作线程完成执行后再继续。这一步是必要的,以确保所有线程在析构函数返回之前都已安全地终止,从而避免资源泄漏或未定义行为。
构造函数
首先,构造函数接受一个参数 threads,表示要创建的线程数量。初始化列表中将 stop 标志设置为 false,表示线程池开始时不停止。
接下来,使用一个 for 循环创建指定数量的线程。每个线程通过 workers.emplace_back 方法添加到 workers 容器中。每个线程执行一个无限循环 for(;;),不断从任务队列中提取任务并执行。
在每个线程的循环内部,首先声明一个 std::function<void()> 类型的变量 task,用于存储从任务队列中提取的任务。然后,使用 std::unique_lock<std::mutex> 锁住 queue_mutex 互斥量,确保对任务队列的访问是线程安全的。
接下来,线程调用 condition.wait 方法等待条件变量,直到 stop 标志为 true 或任务队列不为空。如果 stop 标志为 true 且任务队列为空,线程将返回并终止执行。否则,从任务队列中取出一个任务并将其移到 task 变量中,然后从队列中移除该任务。
最后,调用 task() 执行提取的任务。这个过程在每个线程中不断重复,直到线程池被停止。
通过这种方式,线程池能够并发地处理多个任务,并在需要时安全地停止所有线程。