对线程池的理解

首先我们聊一聊为什么要用线程池?在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_;  
};

解读代码

  1. 首先看构造函数,设定了线程池里的线程数为8,并创建了pool。
  2. 接下来创建8个子线程,创建一个锁然后进入死循环,循环里只要任务不为空就不断的从任务队列里取出数据,取完后再移除任务。
  3. 为了确保互斥锁是没有被上过锁的,所以unlock一次。
  4. 要执行的任务。
  5. detach设置县城分离,就是为了不让父线程对它进行释放。
  6. 如果pool是关闭状态,那么就跳出死循环
  7. 否则就说明池子是空的,这时通过条件变量阻塞在那里,当在池子里放进任务时调用了AddTask,这时AddTask里的notify_one()就唤醒一个线程。
    构造函数的explicit就是为了防止调用构造函数时隐式转换。就是在创建对象时只能用构造函数的方式去创建。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值