C++线程池是一种并发编程技术,它通过预先创建一组线程来管理并发任务,从而减少资源占用和提高程序性能。
线程池的核心思想是预先创建一组线程,这些线程可以重复使用,避免了频繁创建和销毁线程的开销。当新的任务到来时,线程池会选择一个空闲的线程来执行这个任务,如果没有空闲线程,则任务会被放入任务队列中等待执行。
C++11 增加了 std::packaged_task<> 可以用来实现线程池。
1. packaged_task介绍
在标头 | ||
template< class > class packaged_task; // 不予定义 | (1) | (C++11 起) |
template< class R, class ...ArgTypes > | (2) | (C++11 起) |
相关介绍:https://zh.cppreference.com/w/cpp/thread/packaged_task
类模板 std::packaged_task
包装任何可调用 (Callable) 目标(函数、lambda 表达式、bind 表达式或其他函数对象),使得能异步调用它。其返回值或所抛异常被存储于能通过 std::future 对象访问的共享状态中
packaged_task是用来进行任务封装的,将任务封装起来的目的是为了进行线程之间的传递,进行任务的异步处理
#include <stdio.h>
#include <iostream>
#include <future>
#include <functional>
int add(int a, int b) { //两数之和
return a + b;
}
int getVolume(int length, int width, int height) { //根据长宽高求体积
return length * width * height;
}
void test1() {
std::packaged_task<int(int, int)> task(add); //传入函数 add
std::future<int> futureClass = task.get_future();
task(2, 9); //调用函数
int result = futureClass.get(); //获取函数add的返回结果
std::cout << result << "\n";
}
void test2() {
int num1 = 10, num2 = 20;
auto binderClass = std::bind(add, num1, num2); //通过bind绑定函数add
std::packaged_task<int()> task(std::move(binderClass)); //将bind表达式传递给packaged_task.
std::future<int> futureClass = task.get_future();
task(); //调用函数,
int sum = futureClass.get(); //获取函数add的返回结果
std::cout << sum << "\n";
}
void test3() {
std::packaged_task<int()> task(std::bind(add, 100, 200));
std::future<int> futureClass = task.get_future();
std::thread th(std::move(task)); //创建一个线程
th.join();
std::cout << futureClass.get() << "\n"; //得到线程返回结果
}
template<typename FuncName, typename ...Args>
decltype(auto) myTask(FuncName &&func, Args &&...args) {
using ReturnType = decltype(func(args...)); //得到func(args...) 返回类型
auto binder = std::bind(std::forward<FuncName>(func), std::forward<Args>(args)...);
std::packaged_task<ReturnType()> task(std::move(binder));
auto fut = task.get_future();
task();
return fut.get();
}
void test4() {
int sum = myTask(add, 20, 30);
std::cout << "the sum is " << sum << std::endl;
int volume = myTask(getVolume, 10, 20, 30);
std::cout << "the volume is " << volume << std::endl;
}
int main() {
test1();
test2();
test3();
test4();
return 0;
}
线程池的实现步骤:
1. 创建多个线程保存在 vector<thread> m_workers
2. 创建任务队列 taskQueue,其中任务类型为 std::function<void()>, 将用户提交的任务保存在任务队列(taskQueue)中,并保存返回的future。
3. 线程池中的wokers里从任务队列中获取任务,执行任务,
4 从future中获取任务的返回结果
#ifndef __THREADPOOL_H__
#define __THREADPOOL_H__
#include <queue>
#include <functional>
#include <thread>
#include <mutex>
#include <future>
#include <memory>
class ThreadPool {
public:
ThreadPool(int size) {
auto myFunc = [this]() {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(m_mtx);
this->m_cond.wait(lock, [this]() { return this->m_stop || !(m_taskQueue.empty()); }); //等待任务队列非空
if (m_stop && m_taskQueue.empty()) {
return;
}
task = std::move(m_taskQueue.front()); //从任务队列中取出任务
m_taskQueue.pop();
}
task(); //执行任务
}
};
//创建多个线程,从任务队列中取任务并执行
for (int i = 0; i < size; ++i) {
std::thread th = std::thread(myFunc);
m_works.emplace_back(std::move(th));
}
}
~ThreadPool() {
{
std::unique_lock<std::mutex> lock(m_mtx);
m_stop = true;
}
m_cond.notify_all();
for (auto& th : m_works) {
th.join();
}
}
//将函数调用放进一个队列
template<typename Func, typename ...Args>
decltype(auto) InsertQueue(Func&& func, Args && ...args) {
using RetType = decltype(func(args...)); //获取函数的返回类型
auto task = std::make_shared<std::packaged_task<RetType()>>(
std::bind(std::forward<Func>(func), std::forward<Args>(args)...)
); //task 是一个指向packaged_task 的shared_pointer
auto taskResult = task->get_future();
{
std::unique_lock<std::mutex> lock(m_mtx);
m_taskQueue.emplace([task]() { (*task)(); });
m_cond.notify_one();
return taskResult; // 返回future类型taskResult,调用 taskResult.get() 可获取结果
}
}
private:
std::queue<std::function<void()>> m_taskQueue; //将用户提交的任务保存在任务队列里
std::vector<std::thread> m_works; //线程保存在vector中
std::mutex m_mtx;
测试代码:
#include <iostream>
#include <format>
#include "ThreadPool.h"
int func(int i) {
std::cout << std::format("thread id = {}\n", i);
return i * i;
}
int main() {
ThreadPool pool(4); //创建线程池,指定4个线程
std::vector<std::future<int>> results;
for (int i = 0; i < 10; ++i) {
results.emplace_back(pool.InsertQueue(func, i));
}
for (auto&& v : results) {
//std::cout << "Main: results = " << v.get() << std::endl;
std::cout << std::format("Main: reslts = {}\n", v.get());
}
return 0;
}