# Ca1mThreadPool **Repository Path**: ccjabc/ca1m-thread-pool ## Basic Information - **Project Name**: Ca1mThreadPool - **Description**: 线程池。(施磊老师课程) - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2025-10-21 - **Last Updated**: 2025-10-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## C++11语言层级的线程池 - 基于C++11语言层的线程池 - 支持fixed和cached模式的线程池定制 - poolV1是手动实现Any类和Result类支持用户传递任意参数的任务函数(Visual Studio编译时需要设置为C++17标准) - poolV2利用可变参模板编程和引用折叠原理,实现线程池addTask接口 — 支持用户传递任意参数的任务函数(C++14标准) - PixelThreadPool是对poolV2进行CMake构建 - 支持用户传递任意参数的任务函数(C++14标准、Linux系统) 注:Windows平台下最好使用Visual Studio编译(MSVC),mingw64编译器线程模式不易修改使用(Thread model为posix时,支持默认的多线程使用;Thread model为win32时,支持的是Win32线程的API!) ### 基本概念 - 并发:单核,多个线程分配不同时间片 → “看起来共同执行” - 并行:多核,真正的共同执行 - IO密集型:程序里面的指令涉及一些IO操作,比如设备、文件、网络操作(等待客户端连接IO操作是可以阻塞程序) - CPU密集型:程序里面的指令都是做计算用的 - fixed模式线程池:线程池里面的线程个数是固定不变的,一般是ThreadPool创建时根据当前机器的CPU核心数量进行指定 - cached模式线程池:线程池里面的线程个数是可动态增长的,根据任务的数量动态的增加线程的数量 注:多线程程序一定好吗?不一定(对于多核系统而言,多线程程序对于IO密集型和CPU密集型都是适合的;对于单核系统而言,IO密集型程序是适合的,CPU密集型的程序不适合 - 线程的调度有额外开销。) 注:线程越多越好吗?不一定(线程的创建和销毁都是非常“重”的操作,业务端频繁创建和销毁线程资源消耗较大;线程栈本身占用大量内存,每个线程都需要线程栈;线程的上下文切换要占用大量时间。) ![image](./res/img1.png) ### threadpool.h ```cpp #ifndef THREADPOOL_H #define THREADPOOL_H #include #include // 任务抽象基类 class Task { public: // 用户可以自定义任意任务类型,从Task继承,重写run方法,实现自定义任务处理 virtual void run() = 0; }; // 线程池类型 class ThreadPool { public: // ... private: // ... // std::vector threads_; // 智能指针 -> 自动析构裸指针 std::vector> threads_; // 线程列表 std::queue> taskQue_; // 任务队列 }; #endif ``` - 定义任务抽象基类 → 方便用户自定义任务类型,实现自定义任务处理! - 为什么线程列表用智能指针 → 自动析构裸指针! - 为什么定义任务队列时不用`std::queue taskQue_` → 若用`Task*`指针,用户可能创建临时任务对象,提交任务函数`submitTask(concreteTask)`执行完成,任务对象析构,那线程池的基类指针获取已析构的对象,无法访问 → **智能指针保证任务的生命周期!** ### threadpool.cpp ```cpp #include "threadpool.h" #include #include #include // 开启线程池 void ThreadPool::start(int initThreadSize) { // 记录初始线程个数 initThreadSize_ = initThreadSize; // 创建线程对象 for (int i = 0; i < initThreadSize_; i++) { // 创建thread线程对象时,通过绑定器把线程函数给到thread线程对象 auto ptr = std::make_unique(std::bind(&ThreadPool::threadFunc, this)); // 智能指针 threads_.emplace_back(std::move(ptr)); } // 启动所有线程 → 需要执行线程函数 for (int i = 0; i < initThreadSize_; i++) { threads_[i]->start(); // 需要执行一个线程函数 } } void ThreadPool::threadFunc() { // ... } // 启动线程 void Thread::start() { // 创建一个线程来执行一个线程函数 std::thread t(func_); // C++11 线程对象t 和线程函数func_ t.detach(); // 设置分离线程 → 防止线程对象出作用域自动析构 } ``` - 创建线程对象时,通过绑定器把线程函数和thread线程对象绑定 → 将线程函数定义于ThreadPool类中,方便访问私有成员! ```cpp // 给线程池提交任务 -> 用户调用该接口,传入任务对象,生产任务 void ThreadPool::submitTask(std::shared_ptr sp) { // 获取锁 std::unique_locklock(taskQueMtx_); // 线程通信 -> 等待任务队列有空余 wait wait_for 和 wait_until // while (taskQue_.size() == taskQueMaxThresHold_) { notFull_.wait(lock); } // 等价于底下 // notFull_.wait(lock, [&]()->bool { return taskQue_.size() < taskQueMaxThresHold_; }); // 等待状态 // 用户提交任务,最长不能阻塞超过1s,否则判断提交任务失败,返回 if (!notFull_.wait_for(lock, std::chrono::seconds(1), [&]()->bool { return taskQue_.size() < taskQueMaxThresHold_; })) { // wait_for返回为false,说明用户提交任务时最长阻塞超过了1s std::cerr << "task queue is full, submit task fail." << std::endl; return; } // 若有空余,将任务放于任务队列中 taskQue_.emplace(sp); taskSize_++; // 新放任务后,即可在notEmpty_上进行通知 -> 方便“消费者”消费 notEmpty_.notify_all(); } ``` - wait -> 条件变量一直在等 - wait_for -> 条件变量等待时限制时间 - wait_until -> 条件变量等到某个时间 - 此处使用wait_for达到用户提交最长阻塞不超过1s的目的,且wait_for有bool类型返回值 ### 构建可以接收任意类型数据的Any类 - C++17提供了Any类 - 如何让一个类型指向其它任意的类型 -> 利用基类和派生类(数据包含在派生类中),结合模板 - Any类中定义Base*基类指针,基类指针可以指向其派生类对象 ```cpp // Any类型:可以接收任意类型的数据 -> 牛逼! class Any { public: Any() = default; ~Any() = default; Any(const Any&) = delete; Any& operator=(const Any&) = delete; Any(Any&&) = default; Any& operator = (Any&&) = default; // 此构造函数可以让Any类型接收任意其它的数据 template Any(T data) : base_(std::make_unique>(data)) {} // 获取Any对象里面存储的数据 template T cast_() { // 获取base_所指向派生类Derive对象的data成员变量 Derive* pd = dynamic_cast*>(base.get()); // 基类指针转为派生类指针 RTTI if (pd == nullptr) { throw "type is unmatch!"; } return pd->data_; } private: // 基类类型 class Base { public: virtual ~Base() = default; }; // 派生类类型 template class Derive : public Base { public: Derive(T data) : data_(data) {} T data_; // 保存任意的其它类型 }; private: // 定义一个基类的指针 std::unique_ptr base; }; ``` 注:Any类在线程池中的应用 → 用户需要执行计算任务,但返回值类型不一定固定,而线程池中run函数是虚函数,无法和模板一起使用,故需设计Any类! ### 基于可变参模板编程和future类优化 - 使用future来代替Result -> 节省线程池代码(future类型定制用户提交任务的返回值) - 基于可变参模板支持用户提交各种不同类型的任务!