C++11实现的线程池代码分析

72 阅读2分钟

代码来自 腾讯课堂《零声学院C/C++架构师课程》

腾讯课堂 零声教育 ke.qq.com/course/4209… 柚子老师: 2690491738

代码

跟上一篇的C语言实现的线程池不同,该版本使用C++11实现,使用了很多C++11中的新特性,包括任务的异步返回,移动语义,万能引用,完美转发以及可变参数模板等。这里主要分析如何通过线程池启动任务,对于线程池的创建和和退出此类功能这里不做介绍。

运行方式

  • ZERO_ThreadPool tpool;
  • tpool.init(5); //初始化线程池线程数
  • //启动线程方式
  • tpool.start();
  • //将任务丢到线程池中
  • tpool.exec(testFunction, 10);
  • //等待线程池结束
  • tpool.waitForAllDone(1000); //参数<0时, 表示无限等待
  • //外部需要结束线程池时调用
  • tpool.stop();

推送任务到线程池

// 任务节点的定义,线程池中对超时任务做了单独处理,不考虑超时就等于是一个函数对象
    struct TaskFunc
    {
        TaskFunc(uint64_t expireTime) : _expireTime(expireTime)
        { }

        std::function<void()>   _func;
        int64_t                _expireTime = 0;	
    };
    typedef shared_ptr<TaskFunc> TaskFuncPtr;   // 使用智能指针管理,获取一个任务计数+1,执行结束自动销毁
    
    // 万能引用  F&& f :根据传入参数的值性来决定是右值引用还是左值引用
    // 可变模板参数class... Args
    // F是函数对象, Args是函数的参数列表
    // -> std::future<> 的意思是返回值是一个std::future类型,写法与lambda表达式类似
    // decltype(f(args...) 通过表达式对参数为 ‘args...’的函数‘f’返回值类型进行推导
    // decltype功能于auto类似,但是对数组、引用、const等特性的支持更好,能使用auto的地方都可以使用decltype,反之未必
    //decltype中需要传入参数本身而不是类型,也就是说decltype(f(args...))是一个表达式
    
    template <class F, class... Args>
    auto exec(F&& f, Args&&... args) -> std::future<decltype(f(args...))>
    {
        return exec(0,f,args...);
    }
 
 
    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);  
        
        // 定义类型的别名,效果类似typedef定义别名,但是更简洁
        // typedef typename decltype(f(args...))::RetType RetType;
        using RetType = decltype(f(args...));  

        
        //std::make_shared 智能指针shared_ptr的标准构造方式
        // std::packaged_task<RetType()>>(std::function obj) 封装任务
        // std::bind绑定一个可调用对象,同时可以绑定其传入参数,返回一个可调用对象
        // std::forward 与万能引用配合使用,转发参数时保留其值别特征
        auto task = std::make_shared<std::packaged_task<RetType()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));    

        // 封装任务指针,使用一个lambda表达式去执行实际任务
        TaskFuncPtr fPtr = std::make_shared<TaskFunc>(expireTime); 
        fPtr->_func = [task]() {  
            (*task)();  // 执行
        };

        // 将封装好的任务任务指针添加到队列中,并唤醒一个线程去执行
        std::unique_lock<std::mutex> lock(_mutex);
        _tasks.push(fPtr);              
        _condition.notify_one();      

        // std::packaged_task返回的是一个void类型,需要通过get_future得到std::future对象
        return task->get_future();
    }
  • exec()返回的是一个std::future对象,用于异步获取事件的返回值。
  • 采用可变模板参数作为绑定对象的参数列表,使该接口成为一个通用接口,不限制参数类型和数量

执行任务的线程

bool ZERO_ThreadPool::get(TaskFuncPtr& task)
{
    std::unique_lock<std::mutex> lock(_mutex);

    if (_tasks.empty())
    {
        _condition.wait(lock, [this] { return _bTerminate || !_tasks.empty(); });
    }

    if (_bTerminate)
        return false;

    if (!_tasks.empty())
    {
        task = std::move(_tasks.front());  // 使用了移动语义(浅拷贝实现内存资源转移)

        _tasks.pop();

        return true;
    }

    return false;
}

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

            --_atomic;

            //任务都执行完毕了
            std::unique_lock<std::mutex> lock(_mutex);
            if (_atomic == 0 && _tasks.empty())
            {
                _condition.notify_all();  // 这里只是为了通知waitForAllDone
            }
        }
    }
}

饭要一点一点吃,知识要一点一点学...