多线程编程7. 线程池(2)
前置基础:
多线程编程1. 线程库的基本用法
多线程编程2. 多线程数据共享
多线程编程4. std::condition_variable
多线程编程5. 线程池(1)
多线程编程6. std::future、std::promise、std::packaged_task和std::async
线程池的原理:见 多线程编程5. 线程池(1)
多线程编程5. 线程池(1) 所介绍的线程池只是一个最简单的版本,而一般用户所需要执行的任务函数,参数数量不确定,类型不确定,是否有返回值也不确定,所以想要表示用户的任务函数,需要用到函数模板进行泛化表示。
template<class F, class... Args>
auto add_task(F&& fun_name, Args&&... args) -> std::future<typename std::reslut_of<F(Args...)>::type>;
// 1. 函数模板中的 && 是万能引用,既能表示 & ,也能表示 &&
// 2. std::result_of 是一个函数类型萃取器,它能够推导出函数的返回值类型
// std::result_of<F(Args...)>::type 表示的是返回值类型
最终的线程池实现参照github上一个比较经典的例子(https://github.com/progschj/ThreadPool),值得好好研究一下。
代码实现
class ThreadPool {
public:
ThreadPool(size_t);
~ThreadPool();
// 添加任务,该任务函数是带有返回值的
template<class F, class... Args>
auto enqueue(F&& f, Args&&... args)
->std::future<typename std::result_of<F(Args...)>::type>;
private:
std::vector< std::thread > workers;
std::queue< std::function<void()> > tasks;
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)...)
);
// 解释说明:
// 1. std::packaged_task<return_type()> 表示 packaged_task 包裹的是一个 return_type() 函数
// 2. auto fun = std::bind(std::forward<F>(f), std::forward<Args>(args)...)
// 表示 fun = f(args), 已经将fun与函数 f 以及其输入参数进行绑定,所以 fun 就是一个 return_type() 函数
// 符合 std::packaged_task<return_type()> 对包裹函数类型的要求
// 3. 这句话后相当于给用户添加的函数起一个别名,让其满足 return_value() 的函数类型
// 补充知识点:
// 1. std::bind 表示将一个函数和参数标定后,得到一个新的可调用对象
// 2. std::forward 完美转发: 因为函数参数类型可能是& 也可能是&&,而函数模板都是用 && 来表示
// 利用完美转发能够保证参数原有的左值或右值特性
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");
// 因为任务类型是 void(), 所以此处再用匿名函数进行一次包装
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();
}
int main() {
ThreadPool pool(4);
for (int i = 0; i < 8; ++i) {
pool.enqueue([i] {
std::cout << "Task " << i << " is running in thread " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Task " << i << " is done" << std::endl;
});
}
return 0;
}