操作系统:线程池实现

线程池基于互斥锁,条件变量实现, 符合生产者消费者模型

生产者-消费者模型:

一种常见的并发设计模式,生产者负责产出资源放入缓存区中,消费者则负责从缓存区中获取资源。在线程池的实现过程中,资源则是任务,而消费者是不同的线程。线程从队列中获取任务以后完成。在这个过程中,任务队列需要用互斥锁进行保护,而线程之间的协调则需要条件变量实现。

条件变量:

条件变量用于在线程之间相互通知消息,实现对锁的持有和控制。接受线程会等待发送线程的通知,得到通知以后会继续进行后面的代码,某种程度上也起到一个阻塞的作用,等待生产者放出资源,具体实现思路如下:

(图片来自b站视频,侵权自删)

首先是接受线程等待发送线程写入资源,这个阶段中,接受县城持有互斥锁,并且被条件变量所阻塞,暂时不能写入共享数据。

然后是发送线程写入数据,这个时候是发送线程持有互斥锁,进行写入以后通知条件变量

完成写入以后,接收线程再持有互斥锁,结束阻塞状态继续进行

具体的代码使用如下:

主要有三个api进行使用
wait,notify_one以及notify_all;

//消费者部分
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock,[this]{return false;}); //第二个参数为初始阻塞判断,如果为空则为阻塞状态

//生产者任务
cv.notify_one();//通知一个线程解决问题
cv.notify_all();//通知所有线程解决问题

其实如果完全看代码的话,只需要当成一个线程之间阻塞方法即可

线程池类的代码实现

class ThreadsPool{
public:
    //线程数组
    std::vector<std::thread> threads;
    //任务队列
    std::queue<std::function<void()>> tasks;
    //标志
    bool stop;
    //互斥锁
    std::mutex mtx;
    //条件变量
    std::condition_variable cv;
    //构造函数
    //构造函数需要实现的功能就是:启动每一个线程,并且用条件变量等待任务队列的空缺
    ThreadsPool(int num):stop(false){
        for(int i=0;i<num;i++){
            //emplace_back可以理解为直接调用构造函数
            threads.emplace_back([this,i]{
                while(1){
                    //创建锁
                    std::unique_lock<std::mutex> lock(this->mtx);
                    //启动条件变量, 如果程序已经停止执行或者任务队列不为空,则可以继续执行,否则就进行等待
                    this->cv.wait(lock,[this]{return stop||!tasks.empty();});

                    //如果是程序停止了, 就消除这个线程
                    if(stop){
                        return;
                    }

                    //从队列中取出任务, 并且进行执行,
                    std::function<void()> task(std::move(tasks.front()));
                    tasks.pop();
                    //已经取出, 取消锁定, 并且执行任务
                    lock.unlock();
                    task();
                    cout<<"线程"<<i<<"进行服务"<<endl;
                }
            });
        }
    }
    //系出构造函数
    ~ThreadsPool(){
        //设置标志位为停止
        {
            std::unique_lock<std::mutex> lock(this->mtx);
            this->stop=true;
        }
        //通知所有线程启动,完成所有任务
        cv.notify_all();
        //等待所有线程结束,等待所有线程结束
        for(auto& t : threads){
            t.join();
        }

    }
    //向线程池中加入任务的方法
    template<typename Func, typename... Args>
    void enqueue(Func func,Args... args){
        //放入一个任务
        {
            std::unique_lock<std::mutex> lock(this->mtx);
            tasks.emplace([=](){func(args...);});
        }
        //通知条件变量,启动一个线程来执行任务
        cv.notify_one();
    }


};

案例如图所示

补充:

用到了一些cpp11的语法,可能不太常用

lambad表达式:写法为

[]()->returntype{}
//[]为捕获参数,如果是[=]则代表捕获上下文中所有参数
//()为传入参数

但是其实也可以包括一些简写类型
比如
[]();

emplace相比于push,是直接调用构造函数

move用作移动,可以提升一些性能呢个

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值