线程池中两个容器:
线程容器threads_:两种模式:fixed、cached模式
std::unordered_map<int, std::unique_ptr<Thread>> threads_; // 线程列表 int initThreadSize_; // 初始的线程数量 int threadSizeThreshHold_; // 线程数量上限阈值 std::atomic_int curThreadSize_; // 记录当前线程池里面线程的总数量 std::atomic_int idleThreadSize_; // 记录空闲线程的数量
任务容器taskQue_:任务类型通过继承多态,线程池中提供task抽象基类,提供一个纯虚函数run,我们在调用的时候从task继承一个mytask,重写一下run函数
std::queue<std::shared_ptr<Task>> taskQue_; // 任务队列 std::atomic_int taskSize_; // 任务的数量 int taskQueMaxThreshHold_; // 任务队列数量上限阈值
生产者、消费者模型:
线程队列会去任务队列中取出任务拿来执行,外部用户线程会向任务队列里提交任务,内部设计线程通信,使用mutex+conditionvariable互斥锁+条件变量。
std::mutex taskQueMtx_; // 保证任务队列的线程安全 std::condition_variable notFull_; // 表示任务队列不满 std::condition_variable notEmpty_; // 表示任务队列不空 std::condition_variable exitCond_; // 等到线程资源全部回收
线程类定义
// 线程类型 class Thread { public: // 线程函数对象类型 using ThreadFunc = std::function<void(int)>; // 线程构造 Thread(ThreadFunc func); // 线程析构 ~Thread(); // 启动线程 void start(); // 获取线程id int getId()const; private: ThreadFunc func_; static int generateId_; int threadId_; // 保存线程id };
线程池类定义
class ThreadPool { public: // 线程池构造 ThreadPool(); // 线程池析构 ~ThreadPool(); // 设置线程池的工作模式 void setMode(PoolMode mode); // 设置task任务队列上线阈值 void setTaskQueMaxThreshHold(int threshhold); // 设置线程池cached模式下线程阈值 void setThreadSizeThreshHold(int threshhold); // 给线程池提交任务 Result submitTask(std::shared_ptr<Task> sp); // 开启线程池 void start(int initThreadSize = std::thread::hardware_concurrency()); //线程池本身不需要拷贝构造和赋值 所以把拷贝构造函数和=运算符重载函数禁用 ThreadPool(const ThreadPool&) = delete; ThreadPool& operator=(const ThreadPool&) = delete; private: // 定义线程函数 这个是每个线程被创建后需要执行的任务 void threadFunc(int threadid); // 检查pool的运行状态 bool checkRunningState() const; private: // std::vector<std::unique_ptr<Thread>> threads_; // 线程列表 std::unordered_map<int, std::unique_ptr<Thread>> threads_; // 线程列表 int initThreadSize_; // 初始的线程数量 int threadSizeThreshHold_; // 线程数量上限阈值 std::atomic_int curThreadSize_; // 记录当前线程池里面线程的总数量 std::atomic_int idleThreadSize_; // 记录空闲线程的数量 std::queue<std::shared_ptr<Task>> taskQue_; // 任务队列 std::atomic_int taskSize_; // 任务的数量 int taskQueMaxThreshHold_; // 任务队列数量上限阈值 std::mutex taskQueMtx_; // 保证任务队列的线程安全 std::condition_variable notFull_; // 表示任务队列不满 std::condition_variable notEmpty_; // 表示任务队列不空 std::condition_variable exitCond_; // 等到线程资源全部回收 PoolMode poolMode_; // 当前线程池的工作模式 std::atomic_bool isPoolRunning_; // 表示当前线程池的启动状态 };
线程池工作流程:
1.定义线程池对象
Thread Pool pool;
2.选择线程数量模式
Pool.setMode(fixed(default)|cached);
3.开启线程池
这里需要传入线程池开启线程数量,在调用
start
函数时,如果不提供initThreadSize
参数,则默认使用当前系统支持的并发线程数作为初始线程数量。void start(int initThreadSize = std::thread::hardware_concurrency()); // 开启线程池 void ThreadPool::start(int initThreadSize) { // 设置线程池的运行状态 isPoolRunning_ = true; // 记录初始线程个数 initThreadSize_ = initThreadSize; curThreadSize_ = initThreadSize; // 创建线程对象 for (int i = 0; i < initThreadSize_; i++) { // 创建thread线程对象的时候,把线程函数给到thread线程对象 // 这个this指针指的是threadpool对象 auto ptr = std::make_unique<Thread>(std::bind(&ThreadPool::threadFunc, this, std::placeholders::_1)); int threadId = ptr->getId(); threads_.emplace(threadId, std::move(ptr)); // threads_.emplace_back(std::move(ptr)); } // 启动所有线程 std::vector<Thread*> threads_; for (int i = 0; i < initThreadSize_; i++) { threads_[i]->start(); // 需要去执行一个线程函数 idleThreadSize_++; // 记录初始空闲线程的数量 } }
开启线程池,创建好设定数量的线程对象放入线程容器(threads_),并将线程需要执行的函数(threadFunc)通过bind绑定器绑定给线程。
启动每个线程:
void Thread::start() { // 创建一个线程来执行一个线程函数 pthread_create std::thread t(func_, threadId_); // C++11来说 线程对象t 和线程函数func_ t.detach(); // 设置分离线程 pthread_detach pthread_t设置成分离线程 }
4. 给线程池提交任务(生产者)
// 给线程池提交任务 用户调用该接口,传入任务对象,生产任务 Result ThreadPool::submitTask(std::shared_ptr<Task> sp) { // 获取锁 std::unique_lock<std::mutex> lock(taskQueMtx_); // 线程的通信 等待任务队列有空余 //wait 一直等 等到满足函数才停止 //wait_for + 持续等待的时间 等条件满足但是 设置最多等待时间 等十秒 //wait_until + 时间终止的点 等到下星期一 /* while (taskQue_.size() == taskQueMaxThreshHold_) { notFull_.wait(lock); } */ // 用户提交任务,最长不能阻塞超过1s,否则判断提交任务失败,返回 if (!notFull_.wait_for(lock, std::chrono::seconds(1), [&]()->bool { return taskQue_.size() < (size_t)taskQueMaxThreshHold_; })) { // 表示notFull_等待1s种,条件依然没有满足 std::cerr << "task queue is full, submit task fail." << std::endl; // return task->getResult(); // Task Result 线程执行完task,task对象就被析构掉了 return Result(sp, false); } // 如果有空余,把任务放入任务队列中 taskQue_.emplace(sp); taskSize_++; // 因为新放了任务,任务队列肯定不空了,在notEmpty_上进行通知,赶快分配线程执行任务 notEmpty_.notify_all(); // cached模式 任务处理比较紧急 场景:小而快的任务 需要根据任务数量和空闲线程的数量,判断是否需要创建新的线程出来 if (poolMode_ == PoolMode::MODE_CACHED && taskSize_ > idleThreadSize_ && curThreadSize_ < threadSizeThreshHold_) { std::cout << ">>> create new thread..." << std::endl; // 创建新的线程对象 auto ptr = std::make_unique<Thread>(std::bind(&ThreadPool::threadFunc, this, std::placeholders::_1)); int threadId = ptr->getId(); threads_.emplace(threadId, std::move(ptr)); // 启动线程 threads_[threadId]->start(); // 修改线程个数相关的变量 curThreadSize_++; idleThreadSize_++; } // 返回任务的Result对象 return Result(sp); // return task->getResult(); }
5. 线程执行函数(消费者)
// 定义线程函数 线程池的所有线程从任务队列里面消费任务 void ThreadPool::threadFunc(int threadid) // 线程函数返回,相应的线程也就结束了 { auto lastTime = std::chrono::high_resolution_clock().now(); // 所有任务必须执行完成,线程池才可以回收所有线程资源 for (;;) //不断循环取出任务 { std::shared_ptr<Task> task; { // 先获取锁 std::unique_lock<std::mutex> lock(taskQueMtx_); std::cout << "tid:" << std::this_thread::get_id() << "尝试获取任务..." << std::endl; // cached模式下,有可能已经创建了很多的线程,但是空闲时间超过60s,应该把多余的线程 // 结束回收掉(超过initThreadSize_数量的线程要进行回收) // 当前时间 - 上一次线程执行的时间 > 60s // 每一秒中返回一次 怎么区分:超时返回?还是有任务待执行返回 // 锁 + 双重判断 while (taskQue_.size() == 0) { // 线程池要结束,回收线程资源 if (!isPoolRunning_) { threads_.erase(threadid); // std::this_thread::getid() std::cout << "threadid:" << std::this_thread::get_id() << " exit!" << std::endl; exitCond_.notify_all(); return; // 线程函数结束,线程结束 } if (poolMode_ == PoolMode::MODE_CACHED) { // 条件变量,超时返回了 if (std::cv_status::timeout == notEmpty_.wait_for(lock, std::chrono::seconds(1))) { auto now = std::chrono::high_resolution_clock().now(); auto dur = std::chrono::duration_cast<std::chrono::seconds>(now - lastTime); if (dur.count() >= THREAD_MAX_IDLE_TIME && curThreadSize_ > initThreadSize_) { // 开始回收当前线程 // 记录线程数量的相关变量的值修改 // 把线程对象从线程列表容器中删除 没有办法 threadFunc《=》thread对象 // threadid => thread对象 => 删除 threads_.erase(threadid); // std::this_thread::getid() curThreadSize_--; idleThreadSize_--; std::cout << "threadid:" << std::this_thread::get_id() << " exit!" << std::endl; return; } } } else { // 等待notEmpty条件 notEmpty_.wait(lock); } //if (!isPoolRunning_) //{ // threads_.erase(threadid); // std::this_thread::getid() // std::cout << "threadid:" << std::this_thread::get_id() << " exit!" // << std::endl; // exitCond_.notify_all(); // return; // 结束线程函数,就是结束当前线程了! //} } idleThreadSize_--; std::cout << "tid:" << std::this_thread::get_id() << "获取任务成功..." << std::endl; // 从任务队列种取一个任务出来 task = taskQue_.front(); taskQue_.pop(); taskSize_--; // 如果依然有剩余任务,继续通知其它得线程执行任务 if (taskQue_.size() > 0) { notEmpty_.notify_all(); } // 取出一个任务,进行通知,通知可以继续提交生产任务 notFull_.notify_all(); } // 添加作用域 这里就应该把锁释放掉 // 不然就需要执行完任务才能释放锁了 // 将任务从队列中取出 和 执行任务分开 // 当前线程负责执行这个任务 if (task != nullptr) { // task->run(); // 执行任务;把任务的返回值setVal方法给到Result task->exec(); } idleThreadSize_++; lastTime = std::chrono::high_resolution_clock().now(); // 更新线程执行完任务的时间 } }