C++线程池实现

主要功能

提供线程来执行任务。

核心思路

  • 本线程池核心构成就是任务队列存放线程的容器,并且利用锁机制来进行对两者进行操作;

  • 实现任务执行函数,由线程来进行执行;

  • 利用到了C++11 的一些新特性,如lambda表达式atomic原子变量完美转发std::forward std::unique_lock<>等,可作为C++11复习的代码。

具体内容看实现代码


代码实现

ThreadPool.h
#ifndef _THREADPOOL_H_
#define _THREADPOOL_H_

#include <future>
#include <functional>
#include <iostream>
#include <queue>
#include <mutex>
#include <memory>
#include <sys/time.h>

using namespace std;

void getNow(timeval *tv);
int64_t getNowMs();

#define TNOW getNow()
#define TNOWMS getNowMs()

class ThreadPool
{
protected:
    struct TaskFunc
    {
        TaskFunc(uint64_t expireTime) : _expireTime(expireTime)
        {}

        std::function<void()> _func; //函数对象类,其模板参数是函数的类型
        int64_t _expireTime = 0;
    };

    typedef shared_ptr<TaskFunc> TaskFuncPtr;

public:
    ThreadPool();
    virtual ~ThreadPool();
    bool init(size_t num);
    size_t getThreadNum()
    {
        std::unique_lock<std::mutex> lock(mutex_); //构造时候使用mutex_加锁,析构时自动解锁
        return threads_.size();
    } 

    /// @brief 获取线程池任务数
    size_t getJobNum()
    {
        std::unique_lock<std::mutex> lock(mutex_);
        return tasks_.size();
    }

    /// @brief 停止所有线程
    void stop();

    /// @brief 启动所有线程
    bool start();

    //用线程池启用任务(F是function,Args是参数)
    template <class F, class... Args>
    auto exec(F &&f, Args &&...args) -> std::future<decltype(f(args...))>
    {
        return exec(0, f, args...);
    }

    //timeoutMs表示超时时间,为0表示,不做超时控制;若任务超时,此任务将被丢弃
    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); //获取过期时间点
        
        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<TaskFunc>(expireTime);
        fPtr->_func = [task]() { 
            (*task)();  // 等同于调用f(args...)
        };

        std::unique_lock<std::mutex> lock(mutex_);
        tasks_.push(fPtr);

        //唤醒某个等待(wait)线程。如果当前没有等待线程,则该函数什么也不做,如果同时存在多个等待线程,则唤醒某个线程是不确定的(unspecified)。
        condition_.notify_one();

        return task->get_future(); // 返回任务执行后的结果
    }

    //等待任务队列所有工作结束
    //millsecond 等待时间,-1:表示阻塞等待
    //true if AllDone,false if timeout
    bool waitForAllDone(int millsecond = -1);

protected:
    bool getTask(TaskFuncPtr &task);

    bool isTerminate() { return bTerminate_; }

    void run();
protected:
    //任务队列
    queue<TaskFuncPtr> tasks_;

    std::vector<std::thread *> threads_; //

    std::mutex mutex_; //用来锁对threads_ 和 tasks_ 的操作
    std::condition_variable condition_;
    size_t threadNum_;
    bool bTerminate_;

    std::atomic<int> atomic_{0}; //
};
#endif

注解说明

  • packaged_task 、bind、forward的解释,代码如下

    auto task = std::make_shared<std::packaged_task<RetType()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
    
    • 当我们将一个右值引用传入函数时,他在实参中有了命名,所以继续往下传或者调用其他函数时,根据C++ 标准的定义,这个参数变成了一个左值;
    • std::forward完美转发根据右值判断的推倒,调用forward 传出的值,若原来是一个右值,那么他转出来就是一个右值,否则为一个左值;
    • std::packaged_task<>是一个打包任务的类,模板参数是函数签名。 也很类似于std::function,不过这个类对象不过这个类对象有get_future()函数;
    • get_future()函数封装一个任务函数,返回值为RetType,参数为空;
    • bind将参数args…绑定到函数f上,之后调用task() 就等价于调用f(args...)
  • std::future对象提供访问异步操作结果的机制,通过其中get()函数很轻松解决从异步任务中返回结果。

  • 原子变量,实现不加锁完成线程同步,效率比对数据加锁操作平均要高。

    std::atomic<int> atomic_{0};
    
ThreadPool.cpp
#include "ThreadPool.h"

ThreadPool::ThreadPool()
    :threadNum_(1), bTerminate_(false)
{
}

ThreadPool::~ThreadPool()
{
    stop();
}

bool ThreadPool::init(size_t num)
{
    std::unique_lock<std::mutex> lock(mutex_);

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

    threadNum_ = num;
    return true;
}

void 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()) // joinable()用于检测线程是否有效
        {
            threads_[i]->join(); // 等待线程退出(阻塞
        }

        delete threads_[i];
        threads_[i] = NULL;
    }

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

bool 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(&ThreadPool::run, this));
    }
    
    return true;
}

bool ThreadPool::getTask(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 ThreadPool::run()
{
    while(!isTerminate())
    {
        TaskFuncPtr task;
        bool ok = getTask(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(); 
            }
        } 
    }
}

bool ThreadPool::waitForAllDone(int millsecond)
{
    std::unique_lock<std::mutex> lock(mutex_);

    if(tasks_.empty()) return true;

    if(millsecond < 0)
    {
        // 第二个参数 Predicate, 只有当pred为false时,wait才会阻塞当前线程
        condition_.wait(lock, [this]
                        { return tasks_.empty(); }); //任务队列不为空则阻塞。

        return true;
    }
    else 
    {
        // 
        return condition_.wait_for(lock, std::chrono::milliseconds(millsecond), [this]
                                    { return tasks_.empty(); });
    }
}

int gettimeofday(struct timeval &tv) 
{
    return ::gettimeofday(&tv, 0);
}

void getNow(timeval *tv)
{
    gettimeofday(*tv);
}

int64_t getNowMs()
{
    struct timeval tv;
    getNow(&tv);

    return tv.tv_sec * (int64_t)1000 + tv.tv_usec / 1000;
}

解释说明

  • std::condition_variable::wait(_Lck, _Pred)

先unlock之前获得的mutex,第二个参数_Pred为false才会阻塞当前线程;

把当前线程添加到等待线程列表中,该线程会持续 block 直到被 notify_all()notify_one() 唤醒。

且当 pred 为 true 时才会被解除阻塞,执行流程类似于:

while (!pred()) {
       wait(lock);
}
main.cpp

测试线程池

#include "ThreadPool.h"

int testInt(int i)
{
    return i;
}

void test()
{
    ThreadPool tpool;
    tpool.init(5);

    tpool.start();

    auto f = tpool.exec(testInt, 10);

    cout << f.get() << endl;

    tpool.waitForAllDone(1000);

    tpool.stop();
    
}

int main()
{

    test();
    return 0;
}
编译调试

这里我使用CMake,也当做是复习一下

程序文件结构如下

jyhlinux@ubuntu:~/share/threadpool_cpp$ tree
.
├── build
├── CMakeLists.txt
├── main.cpp
├── ThreadPool.cpp
└── ThreadPool.h

CMakeLists.txt

cmake_minimum_required(VERSION 2.6)
PROJECT(THREADPOOL)

ADD_EXECUTABLE(threadpool main.cpp ThreadPool.cpp)

TARGET_LINK_LIBRARIES(threadpool  pthread) 

编译过程

jyhlinux@ubuntu:~/share/threadpool_cpp$ cd build
jyhlinux@ubuntu:~/share/threadpool_cpp/build$ cmake ..
jyhlinux@ubuntu:~/share/threadpool_cpp/build$ make
jyhlinux@ubuntu:~/share/threadpool_cpp/build$ ./threadpool 
10
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值