用C++封装线程池

来源:互联网 发布:mac air快捷键大全 编辑:程序博客网 时间:2024/05/16 16:55

先上代码,代码来自GitHub。这段代码用了大量C++ 11新特性,并且非常晦涩难懂,接下来会对每个细节逐个解释。

ThreadPool.h

#ifndef THREAD_POOL_H#define THREAD_POOL_H#include <vector>#include <queue>#include <memory>#include <thread>#include <mutex>#include <condition_variable>#include <future>#include <functional>#include <stdexcept>class ThreadPool {public:    ThreadPool(size_t);    template<class F, class... Args>    auto enqueue(F&& f, Args&&... args)        ->std::future<typename std::result_of<F(Args...)>::type>;    ~ThreadPool();private:    // need to keep track of threads so we can join them    std::vector< std::thread > workers;    // the task queue    std::queue< std::function<void()> > tasks;    // synchronization    std::mutex queue_mutex;    std::condition_variable condition;    bool stop;};// the constructor just launches some amount of workersinline ThreadPool::ThreadPool(size_t threads)    : stop(false){    for (size_t i = 0; i<threads; ++i)        workers.emplace_back(        [this]    {        for (;;)        {            std::function<void()> task;            {                std::unique_lock<std::mutex> lock(this->queue_mutex);                this->condition.wait(lock,                    [this]{ return this->stop || !this->tasks.empty(); });                if (this->stop && this->tasks.empty())                    return;                task = std::move(this->tasks.front());                this->tasks.pop();            }            task();        }    }    );}// add new work item to the pooltemplate<class F, class... Args>auto ThreadPool::enqueue(F&& f, Args&&... args)-> std::future<typename std::result_of<F(Args...)>::type>{    using return_type = typename std::result_of<F(Args...)>::type;    auto task = std::make_shared< std::packaged_task<return_type()> >(        std::bind(std::forward<F>(f), std::forward<Args>(args)...)        );    std::future<return_type> res = task->get_future();    {        std::unique_lock<std::mutex> lock(queue_mutex);        // don't allow enqueueing after stopping the pool        if (stop)            throw std::runtime_error("enqueue on stopped ThreadPool");        tasks.emplace([task](){ (*task)(); });    }    condition.notify_one();    return res;}// the destructor joins all threadsinline ThreadPool::~ThreadPool(){    {        std::unique_lock<std::mutex> lock(queue_mutex);        stop = true;    }    condition.notify_all();    for (std::thread &worker : workers)        worker.join();}#endif

main.c

#include <iostream>#include <vector>#include <chrono>#include "ThreadPool.h"int main(){    ThreadPool pool(4);    std::vector< std::future<int> > results;    for (int i = 0; i < 8; ++i) {        results.emplace_back(            pool.enqueue([i] {            std::cout << "hello " << i << std::endl;            std::this_thread::sleep_for(std::chrono::seconds(1));            std::cout << "world " << i << std::endl;            return i*i;        })            );    }    for (auto && result : results)        std::cout << result.get() << ' ';    std::cout << std::endl;    getchar();    return 0;}

详细分析

ThreadPool.h 开始逐段分析。

#include <vector>               // 数组#include <queue>                // 队列#include <memory>               // 内存操作#include <thread>               // 线程相关#include <mutex>                // 互斥量#include <condition_variable>   // 条件变量#include <future>               // 从异步获取结果#include <functional>           // 包装函数为对象#include <stdexcept>            // 异常相关

>详解<

1 < thread>: 是 C++ 11的新特性,主要包含了线程对象std::thread的构造。
2 < mutex>: C++ 11新特性,主要包含各种Mutex的类的构造,主要是std::mutex。
3 < condition_variable>: C++ 11新特性, 包含多线程中常用的条件变量的声明,例如notify_one、wait、wait_for等等。
4 < future>: C++ 11新特性,可以获取异步任务的结果,可用来实现同步。包括std::sync和std::future。
5 < functional>: C++ 11增加了一些新特性,简单来说可以实现函数到对象的绑定,如bind()函数。


然后是对线程池对象ThreadPool的声明:

class ThreadPool {public:    ThreadPool(size_t);    // 构造函数    template<class F, class... Args>    auto enqueue(F&& f, Args&&... args)        ->std::future<typename std::result_of<F(Args...)>::type>; // 模板,下面会有详解    ~ThreadPool(); // 析构private:    // need to keep track of threads so we can join them    std::vector< std::thread > workers;    // 线程数组    // the task queue    std::queue< std::function<void()> > tasks; // 任务队列,几乎所有的线程池都会有任务队列    // synchronization    std::mutex queue_mutex;    // 互斥量    std::condition_variable condition; // 条件变量    bool stop; // 停止或开始任务};

>详解<

1 template < class F, class… Args> auto enqueue(F&& f, Args&&… args) -> std::future< typename std::result_of< F(Args…)>::type>;
理解了这一句,这个程序就差不多弄懂了。
首先,这是一个函数模板,而不是类模板。
template<> 部分: template < class F, class… Args>。class… Args代表接受多个参数。
返回类型: auto
函数名: enqueue
形参表: (F&& f, Args&&… args)。&&是C++ 11新特性,代表右值引用。
不明觉厉: -> std::future< typename std::result_of< F(Args…)>::type>。这个->符号其实用到了C++ 11中的lamda表达式,后面的内容代表函数的返回类型。
总的来说就是,这句话声明了一个名为enqueue()的函数模板,它的模板类型为class F以及多个其他类型Args,它的形参是一个F&&类型的f以及多个Args&&类型的args,最后这个函数返回类型是std::future< typename std::result_of < F(Args…)>::type >。有点非人类。
对于这个冗长的返回类型,又可以继续分析:
std::future在前面提到过了,它本身是一个模板,包含在 < future>中。通过std::future可以返回这个A类型的异步任务的结果。
std::result_of\::type就是这段代码中的A类型。result_of获取了someTask的执行结果的类型。
F(Args…)_就是这段代码的someTask,即函数F(Args…)。
所以最后这个模板函数enqueue()的返回值类型就是F(Args…)的异步执行结果类型。
2 std::vector < std::thread> workers: 像注释说的那样,用来保存线程对象
3 std::queue < std::function\void()>> tasks: 任务队列
4 queue_mutex和condition: 线程同步需要的变量


接下来是构造函数ThreadPool(size_t threads)

// the constructor just launches some amount of workersinline ThreadPool::ThreadPool(size_t threads)    : stop(false){    for (size_t i = 0; i<threads; ++i)        workers.emplace_back(        [this]    {        for (;;)        {            std::function<void()> task;            {                std::unique_lock<std::mutex> lock(this->queue_mutex);                this->condition.wait(lock,                    [this]{ return this->stop || !this->tasks.empty(); });                if (this->stop && this->tasks.empty())                    return;                task = std::move(this->tasks.front());                this->tasks.pop();            }            task();        }    }    );}

构造函数声明为inline, 函数体内存在lambda表达式。
>详解<

1 inline: 类似宏定义,会建议编译器把函数以直接展开的形式放入目标代码而不是以入栈调用的形式。通常函数体内代码比较长或者体内出现循环时不宜使用内联,这样会造成代码膨胀。具体参考《Effective C++》: 第30条 。
2 workers.emplace_back([this]{…});
emplace_back()与push_back()类似,但是前者更适合用来传递对象,因为它可以避免对象作为参数被传递时在拷贝成员上的开销。
这里emplace_back()了一个lambda表达式[this]{…}。lambda表达式本身代表一个匿名函数(即没有函数名的函数),通常格式为[捕获列表](参数列表)->return 返回类型{函数体}。而在本代码中的lambda表达式是作为一个线程放入workers[]中。
这个线程是个for(;;)循环。
3 for(;;)里面: 每次循环首先声明一个std::function< void()> task,task是一个可以被封装成对象的函数,在此作为最小任务单位。然后用{}添加了一个作用域。
4 作用域里面: 在这个作用域中进行了一些线程上锁和线程状态的判断。
5 lock(this->queue_mutex): 声明上锁原语
6 condition.wait(lock, [this]{…}): 使当前线程进入阻塞状态: 当第二个参数为false时,wait()会阻塞当前线程,为true时解除阻塞;在本例中的条件就是,当线程池运行或者任务列表为空时,线程进入阻塞态。
然后判断,如果线程池运行或者任务列表为空则继续后续操作,否则退出这个[this]{…}线程函数。
std::move()是移动构造函数,相当于效率更高的拷贝构造函数。最后将tasks[]任务队列的第一个任务出栈。
7 离开作用域: 然后执行task(),当前一轮循环结束。


添加新任务到线程池中的模板函数enqueue()的实现:

// add new work item to the pooltemplate<class F, class... Args>auto ThreadPool::enqueue(F&& f, Args&&... args)-> std::future<typename std::result_of<F(Args...)>::type>{    using return_type = typename std::result_of<F(Args...)>::type;    auto task = std::make_shared< std::packaged_task<return_type()> >(        std::bind(std::forward<F>(f), std::forward<Args>(args)...)        );    std::future<return_type> res = task->get_future();    {        std::unique_lock<std::mutex> lock(queue_mutex);        // don't allow enqueueing after stopping the pool        if (stop)            throw std::runtime_error("enqueue on stopped ThreadPool");        tasks.emplace([task](){ (*task)(); });    }    condition.notify_one();    return res;}

模板函数体外的部分已经分析过了,来看看函数体内做了什么。
>详解<

1 using … = typename …; 功能类似typedef。将return_type声明为一个result_of< F(Args…)>::type类型,即函数F(Args…)的返回值类型。
2 make_shared < packaged_task < >>(bind()): 又是复杂的嵌套。
make_shared : 开辟()个类型为<>的内存
packaged_task : 把任务打包,这里打包的是return_type
bind : 绑定函数f, 参数为args…
forward : 使()转化为<>相同类型的左值或右值引用
简单来说,这句话相当于把函数f和它的参数args…打包为一个模板内定义的task,便于后续操作。
3 res = task->get_future(): 与模板函数的返回类型一致,是函数异步的执行结果。
4 新作用域: 先是一个加锁原语lock()。
然后是个异常处理,如果停止的话抛出一个运行时异常。
最后,向任务列表插入这个任务[task](){(*task)();}。
5 condition.notify_one(): 解除一个正在等待唤醒的线程的阻塞态。
6 返回异步结果res


析构函数:

// the destructor joins all threadsinline ThreadPool::~ThreadPool(){    {        std::unique_lock<std::mutex> lock(queue_mutex);        stop = true;    }    condition.notify_all();    for (std::thread &worker : workers)        worker.join();}

>详解<

1 加锁原语lock(queue_mutex)
2 解除所有线程的阻塞态notify_all()
3 当所有线程执行完毕时返回主线程worker.join()


再看看main.c:

    ThreadPool pool(4);    std::vector< std::future<int> > results;

声明一个有4个线程的线程池。
results[]用来保存异步调用的结果。


调用线程池

for (int i = 0; i < 8; ++i) {        results.emplace_back(            pool.enqueue([i] {            std::cout << "hello " << i << std::endl;            std::this_thread::sleep_for(std::chrono::seconds(1));            std::cout << "world " << i << std::endl;            return i*i;        })            );    }

这个例子里添加到线程池中的任务函数返回类型是int, 函数体是打印hello world和i以及暂停一秒。


for (auto && result : results)        std::cout << result.get() << ' ';    std::cout << std::endl;

将所有的线程返回结果打印出来,打印结果也是异步执行的。