先上代码:
#ifndef THREADPOOL_H #define THREADPOOL_H #include <mutex> #include <condition_variable> #include <queue> #include <thread> #include <functional> class ThreadPool { public: explicit ThreadPool(size_t threadCount = 8): pool_(std::make_shared<Pool>()) { assert(threadCount > 0); //断言 for(size_t i = 0; i < threadCount; i++) { //创建 threadCount个工作线程 std::thread([pool = pool_] { //创建线程 将pool_ 赋值给 pool std::unique_lock<std::mutex> locker(pool->mtx); //创建一个锁 //工作线程的逻辑 while(true) { //不断的去向工作队列请求 if(!pool->tasks.empty()) { //如果工作队列不为空 auto task = std::move(pool->tasks.front());//获取一个任务 pool->tasks.pop(); //队列弹出已经被获取的任务 locker.unlock(); //解锁 task(); //处理任务 locker.lock(); //上锁 } else if(pool->isClosed) break; //如果工作队列为空,并且池子已经关闭,退出 else pool->cond.wait(locker); //如果工作队列为空并且线程池没有关闭,则设置该工作线程阻塞等待 条件变量-1 } }).detach(); //设置线程分离 } } ThreadPool() = default; //采用默认的构造函数 ThreadPool(ThreadPool&&) = default; //采用默认的拷贝构造函数 ~ThreadPool() { if(static_cast<bool>(pool_)) { { std::lock_guard<std::mutex> locker(pool_->mtx); //创建一个锁 pool_->isClosed = true; //关闭线程池 } pool_->cond.notify_all(); //唤醒线程池中所以的线程,线程最终就会进入 else if(pool->isClosed) break; 逻辑,最后退出 } } template<class F> void AddTask(F&& task) { //向线程池中添加任务 { std::lock_guard<std::mutex> locker(pool_->mtx); //创建一个锁 pool_->tasks.emplace(std::forward<F>(task)); //向工作队列中插入一个任务 } pool_->cond.notify_one(); //唤醒一个线程 条件变量+1 } private: struct Pool { std::mutex mtx; //互斥锁 std::condition_variable cond; //条件变量 bool isClosed; //线程池是否关闭 std::queue<std::function<void()>> tasks; //工作队列 }; std::shared_ptr<Pool> pool_; //定义一个 Pool指针 }; #endif //THREADPOOL_H
这段代码是通过c++14写的,我们主要看逻辑关系和实现的原理。
一般线程池的实现的模型:消费者生产者
既然是生产者消费者模型,就设计到了线程同步的问题。。。。
线程同步问题:互斥量,条件变量
我们把代码分为三个部分来看:
这一部分是线程池所需要的一些条件
将互斥锁、条件变量、线程池是否关闭的状态、工作队列封装到一起,然后通过智能指针(共享的)来操作管理这些条件
向生产者向工作队列中添加任务(主线程去完成),并设置唤醒
explicit:防止构造函数出现隐式转换。比如 A a = 8,这样是不允许的
通过while不断让工作线程的请求工作队列,如果工作队列不为空,则获取一个任务并处理。如果工作队列为空并且线程池已经关闭,则直接跳出。如果工作队列不为空并且线程池没有关闭,则阻塞等待被唤醒。
使用线程池可以减少线程的销毁,而且如果不使用线程池的话,来一个客户端就创建一个线程。比如有1000,这样线程的创建、线程之间的调度也会耗费很多的系统资源,所以采用线程池使程序的效率更高。 线程池就是项目启动的时候,就先把线程池准备好。