Linux下C++线程池的实现(分模块讲解)

之前实现了C语言版线程池,这次来实现C++版的线程池(用到了C++11的新特性)。实现整个线程池的思路可以看上期讲解,比较详细

一:关于这个类中所需要的参数

class ZERO_ThreadPool
{
protected:
    struct TaskFunc     //任务的结构体
    {
        TaskFunc(uint64_t expireTime) : _expireTime(expireTime) //传入超时时间
        { }

        std::function<void()>   _func;      //任务
        int64_t                _expireTime = 0;	//超时的绝对时间
    };
    typedef shared_ptr<TaskFunc> TaskFuncPtr;   //指向任务结构体的指针

protected:
    queue<TaskFuncPtr> tasks_;      //存放任务指针的结构体
    std::vector<std::thread*> threads_; //存放线程的容器
    std::mutex                mutex_;       //互斥锁
    std::condition_variable   condition_;   //条件变量
    size_t                    threadNum_;   //线程的数量
    bool                      bTerminate_;      //标志位,标志线程是否退出
    std::atomic<int>          atomic_{ 0 };     //原子变量
 ZERO_ThreadPool();
    virtual ~ZERO_ThreadPool();
    bool init(size_t num);
    size_t getThreadNum();       //获得线程的数量
    size_t getJobNum();      //获得现在任务的数量
    void stop();            //线程退出
    bool start(); // 创建线程

    //这里是没有传入时间
    template <class F, class... Args>
    auto exec(F&& f, Args&&... args) -> std::future<decltype(f(args...))>{}
 
    //插入任务
    template <class F, class... Args>
    auto exec(int64_t timeoutMs, F&& f, Args&&... args) ->std::future<decltype(f(args...))>{}
 
    bool waitForAllDone(int millsecond = -1);
protected:
    bool get(TaskFuncPtr&task);                 //取任务
    bool isTerminate() { return bTerminate_; }  //
    void run();
};

二:关于重要函数的实现

        1、初始化线程池。

bool ZERO_ThreadPool::init(size_t num)      //初始化线程池
{
    std::unique_lock<std::mutex> lock(mutex_);

    if (!threads_.empty())
    {
        return false;
    }

    threadNum_ = num; // 设置线程数量
    return true;
}

        2、线程池的创建。

bool ZERO_ThreadPool::start()
{
    std::unique_lock<std::mutex> lock(mutex_);

    if (!threads_.empty())
    {
        return false;
    }

    for (size_t i = 0; i < threadNum_; i++)     //创建线程
    {
        threads_.push_back(new thread(&ZERO_ThreadPool::run, this)); //将创建好的线程放入到容器中,并且添加线程进入的函数
    }
    return true;
}

        3、线程池的退出。

void ZERO_ThreadPool::stop()        //线程池的退出
{
    {
        std::unique_lock<std::mutex> lock(mutex_);  //加锁
        bTerminate_ = true;     // 触发退出
        condition_.notify_all();        //通知全部的线程
    }

    for (size_t i = 0; i < threads_.size(); i++)
    {
        if(threads_[i]->joinable())
        {
            threads_[i]->join(); // 等线程推出
        }
        delete threads_[i];
        threads_[i] = NULL;
    }

    std::unique_lock<std::mutex> lock(mutex_);
    threads_.clear();
}

        4、消费者使用的函数。

bool ZERO_ThreadPool::get(TaskFuncPtr& task)
{
    std::unique_lock<std::mutex> lock(mutex_); // 也要等锁

    if (tasks_.empty()) // 判断是否任务存在
    {
        condition_.wait(lock, [this] { return bTerminate_  // 要终止线程池 bTerminate_设置为true,外部notify后
                    || !tasks_.empty();  // 任务队列不为空
        }); // notify ->  1. 退出线程池; 2.任务队列不为空
    }

    if (bTerminate_)                //这里要判断一次是不是要执行退出了
        return false;

    if (!tasks_.empty())        //为什么要在判断一次,因为当通知之后,所有线程要在走一遍,避免两个线程同时进入
    {
        task = std::move(tasks_.front());  // 使用了移动语义
        tasks_.pop(); // 释放一个任务
        return true;
    }

    return false;
}

        5、创建好线程池需要走的函数。

void ZERO_ThreadPool::run()  // 执行任务的线程
{
    //调用处理部分
    while (!isTerminate()) // 判断是不是要停止
    {
        TaskFuncPtr task;
        bool ok = get(task);        // 1. 读取任务
        if (ok)
        {
            ++atomic_;
            try
            {
                if (task->_expireTime != 0 && task->_expireTime  < TNOWMS )
                {
                    //超时任务,是否需要处理?
                    cout<<"你怎么超时了!"<<endl;
                }
                else
                {
                    task->_func();  // 2. 执行任务
                }
            }
            catch (...)
            {
            }

            --atomic_;

            //任务都执行完毕了
            std::unique_lock<std::mutex> lock(mutex_);
            if (atomic_ == 0 && tasks_.empty()) // 3.检测是否所有任务都运行完毕
            {
                condition_.notify_all();  // 这里只是为了通知waitForAllDone
            }
        }
    }
}

        6、对于模板函数的理解

首先 template <class F, class... Args> 这行代码:是一个模板声明的开头,它定义了一个模板,该模板可以接受一个函数类型 F 和一个可变数量的参数类型 Args...。这种模板通常用于编写可以接受任何类型函数(包括lambda表达式、函数指针、函数对象等)和任意数量参数的通用函数或类。

这里,F 表示函数类型,而 Args... 是一个参数包,表示任意数量和类型的参数。参数包是C++11及更高版本中引入的一个特性,允许模板接收可变数量的模板参数。对于这个函数来说,接收一个超时时间、可调用对象、参数包以及返回一个future对象。其中使用了decltype来推导类型。然后通过将该任务进行包装操作。

    template <class F, class... Args>
    auto exec(int64_t timeoutMs, F&& f, Args&&... args) -> std::future<decltype(f(args...))>
    {
        int64_t expireTime = (timeoutMs ==0 ? 0: TNOWMS + timeoutMs);   //now time
        using RetType = decltype(f(args...));   //推到返回值
        auto task = std::make_shared<std::packaged_task<RetType()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));

        TaskFuncPtr fPtr = std::make_shared<Task>(expireTime);  // 封装任务指针,设置过期时间
        fPtr->ptr = [task]() {  // 将具体执行的任务全部封装到,这个任务的函数中去。
            (*task)();
        };

        std::unique_lock<mutex> lock(mutex_);
        task_queue.push(fPtr);
        condition.notify_one();
        return task->get_future();
    }

此片文章主要为记录笔记,如要看详细讲解,请看上期内容https://xxetb.xetslk.com/s/2D96kH

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值