首先我们聊一聊为什么要用线程池?在Linux当中有select/poll/epoll这三个I/O复用的方法,我们可以想到如果服务器客户量很大比如说1000个,那么使用多线程和多进程都不合适了,多线程创建需要创建时间,加上执行逻辑再加上销毁使用的时间,这是一个很大的开销。而且cpu也只会调用一个线程去执行,那么我们开1000个就会有更多的开销了,所以这里就提出了线程池。
线程池
线程池它就是一个池子,里面有固定数量的线程如果是CPU密集型应用,则线程池大小设置为:CPU数目+1; 如果是IO密集型应用,则线程池大小设置为:2*CPU数目+1
,线程创建后不会被销毁。
当主线程接收到新任务后把任务放到工作队列里去,如果有空闲的子线程,那就去工作队列里面去取任务然后再运行。
主线程可以往工作队列里放任务,线程池里的线程可以取任务,这就形成了一个生产者消费者模型。工作队列是共享的资源那么就需要线程同步,线程同步就可以用互斥锁(或者其他线程同步的方法)来实现,如果工作队列里没有数据,这时线程池里的线程就不需要去取任务了,这时就用到了条件变量(或者信号量)来让线程停下取任务。
代码如下:
#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);
// 创建threadCount个子线程
for(size_t i = 0; i < threadCount; i++) {
std::thread([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); // 如果队列为空,等待
}
}).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();
}
}
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(); // 唤醒一个等待的线程
}
private:
struct Pool {
// 互斥锁
std::mutex mtx;
// 条件变量
std::condition_variable cond;
// 是否关闭池子
bool isClosed;
// 保存任务的队列
std::queue<std::function<void()> > tasks;
};
// 池子
std::shared_ptr<Pool> pool_;
};
解读代码
- 首先看构造函数,设定了线程池里的线程数为8,并创建了pool。
- 接下来创建8个子线程,创建一个锁然后进入死循环,循环里只要任务不为空就不断的从任务队列里取出数据,取完后再移除任务。
- 为了确保互斥锁是没有被上过锁的,所以unlock一次。
- 要执行的任务。
- detach设置县城分离,就是为了不让父线程对它进行释放。
- 如果pool是关闭状态,那么就跳出死循环
- 否则就说明池子是空的,这时通过条件变量阻塞在那里,当在池子里放进任务时调用了AddTask,这时AddTask里的notify_one()就唤醒一个线程。
构造函数的explicit
就是为了防止调用构造函数时隐式转换。就是在创建对象时只能用构造函数的方式去创建。