线程池实现与性能分析

一、线程池是什么、解决什么问题?

线程池是维持管理一定数量线程的池式结构,与它类似的池式结构还有内存池、连接池,对象池,它们的共同点是对资源进行复用。
之所以要维持一定数量的线程是因为系统的资源有限,随着线程的数量一直增加可能不再带来性能的提升,甚至可能造成负担。同时也避免了重复创建销毁线程。
线程池异步执行耗时任务,不过度占用核心线程,充分利用多核,并发执行核心业务。在nginx 、redis中都有使用线程池。
为了充分利用系统资源,通常在选择线程池线程数量时,若为cpu密集型选择cpu核心数为线程数,若为io密集型选择2倍cpu核心数为线程数量。

二、线程池的构成

线程池由生产者线程、消费者线程和任务队列构成。生产者线程负责发布任务,往任务队列中push任务,消费者线程从任务队列取任务执行。
在这里插入图片描述

三、代码实现

1.面对生产者线程

发布任务到线程池Post
代码如下(示例):

class ThreadPool
{
public:
    //初始化线程池
    explicit ThreadPool(int threads_num){
        for(int i = 0; i < threads_num; ++i){
            workers_.emplace_back([this](void){Worker();});
        }
    }

    //停止线程池
    ~ThreadPool(){
        task_queue_.Cancel();
        for(auto &worker : workers_){
            if(worker.joinable()){
                worker.join();//等待退出
            }
        }
    }

    //发布任务到线程池
    void Post(std::function<void()> task){
        task_queue_.Push(task);   //push和pop都是线程安全的
    }

private:
    //入口函数 消费者线程不断弹出元素
    void Worker(){
        while (true){
            std::function<void()> task;
            if(!task_queue_.Pop(task)){
                break;//退出
            }
            task();//正常弹出 执行任务
        } 
    }
    BlockingQueuePro<std::function<void()>> task_queue_;
    std::vector<std::thread> workers_;
};

2.阻塞队列的设计

之所以选择队列是因为队列是一个双开口结构,生产者对应一个口,消费者对应另一个口,操作队列时间复杂度为O(1),加锁简单。单个阻塞队列模式适合单生产者多消费者模型。
代码如下(示例):

//线程安全的阻塞队列
template <typename T>
class BlockingQueue
{
public:
    BlockingQueue(bool nonblock = false) : nonblock_(nonblock){}
    //入队操作
    void Push (const T &value){
        //在对象构造的时候lock 对象结束稀构的时候自动unlock
        std::lock_guard<std::mutex> lock(mutex_);
        queue_.push(value);
        not_empty_.notify_one();
    }

    //正常pop 弹出元素     消费者取数 
    //异常pop 没有弹出元素 退出
    //通过bool判断是否正常 由参数引用取值
    bool Pop(T &value){
        //pop可能会阻塞 可能需要手动unlock 使用unique_lock
        std::unique_lock<std::mutex> lock(mutex_);
        //wait里面:
        // 1. mutex_.unlock()
        // 2. queue_empty && !nonblock 线程在 wait 中阻塞
        // notify_one notify_all的时候 唤醒线程 通常线程就在这休眠
        // 3. 假设满足notify_one notify_all条件 mutex_.lock()
        // 4. 不满足条件 回到 2
        not_empty_.wait(lock,[this]{return !queue_.empty() || nonblock_;});
        if(queue_.empty()) return false;
        
        value = queue_.front();
        queue_.pop();
        return true;
    }

    // 解除阻塞在当前队列的线程 退出的时候
    void Cancel(){
        std::lock_guard<std::mutex> lock(mutex_);
        nonblock_ = true;
        not_empty_.notify_all();
    }

private:
    bool nonblock_;         //队列是否阻塞
    std::queue<T> queue_;   //使用队列先进先出时间复杂度o(1)
    std::mutex mutex_;      //队列是临界资源 需要加锁
    std::condition_variable not_empty_; //条件变量  用来阻塞线程唤醒线程  用户定义队列为空就会阻塞线程
};

2.阻塞队列的优化

使用双队列结构,生产者对应一个队列,消费者对应一个队列,实现队列切换,减少了锁征用,适合多生产者线程多消费者线程模式。

template <typename T>
class BlockingQueuePro
{

public:
    BlockingQueuePro(bool nonbolck = false) : nonblock_(nonbolck){}
    
    void Push(const T &value){
        std::lock_guard<std::mutex> lock(prod_mutex_);
        prod_queue_.push(value);
        not_empty_.notify_one();
    }

    bool Pop(T &value)
    {
        std::unique_lock<std::mutex> lock(cons_mutex_);
        if(cons_queue_.empty() && SwapQueue_() == 0){
            return false;
        }
        value = cons_queue_.front();
        cons_queue_.pop();
        return true;
    }

    void Cancel()
    {
        std::lock_guard<std::mutex> lock(prod_mutex_);
        nonblock_ = true;
        not_empty_.notify_all();
    }

private:
    int SwapQueue_(){
        std::unique_lock<std::mutex> lock(prod_mutex_);
        not_empty_.wait(lock,[this]{return !prod_queue_.empty() || nonblock_;});
        std::swap(prod_queue_,cons_queue_);
        return cons_queue_.size();
    }
    bool nonblock_;
    std::queue<T> prod_queue_;
    std::queue<T> cons_queue_;
    std::mutex prod_mutex_;
    std::mutex cons_mutex_;
    std::condition_variable not_empty_;
};

此外线程池实现时还需注意将队列和线程池封装分离,使用前置声明代替头文件包含,避免循环依赖,导致改动小部分代码造成大量重新编译。

详细内容可参考www.0voice.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值