用C++封装线程池

先上代码,代码来自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 workers
inline 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 pool
template<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 threads
inline 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 workers
inline 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 pool
template<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 threads
inline 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;

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

  • 23
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
下面是一个简单的C++线程池实现,仅供参考: ```c++ #include <iostream> #include <vector> #include <queue> #include <thread> #include <mutex> #include <condition_variable> #include <functional> #include <future> class ThreadPool { public: ThreadPool(size_t thread_count) : stop(false) { for (size_t i = 0; i < thread_count; ++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(); } } ); } } template<class F, class... Args> auto 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); // 不允许向已停止的线程池中添加新的任务 if (stop) throw std::runtime_error("enqueue on stopped ThreadPool"); tasks.emplace([task]() { (*task)(); }); } condition.notify_one(); return res; } ~ThreadPool() { { std::unique_lock<std::mutex> lock(queue_mutex); stop = true; } condition.notify_all(); for (std::thread& worker : workers) worker.join(); } private: std::vector<std::thread> workers; std::queue<std::function<void()>> tasks; std::mutex queue_mutex; std::condition_variable condition; bool stop; }; ``` 上面的代码中,`ThreadPool`类封装了一个线程池,提供了`enqueue()`方法用于向线程池中添加任务。当调用`enqueue()`方法时,线程池会将任务封装成一个`std::packaged_task`对象,并将其放入任务队列中。工作线程会从任务队列中取出任务并执行,直到线程池被销毁或者调用`stop()`方法停止线程池。 可以使用下面的代码测试线程池的功能: ```c++ #include <chrono> #include <random> int main() { ThreadPool pool(4); std::vector<std::future<int>> results; std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dis(1, 100); for (int i = 0; i < 8; ++i) { results.emplace_back( pool.enqueue([i, dis, &gen] { std::cout << "task " << i << " start" << std::endl; std::this_thread::sleep_for(std::chrono::seconds(dis(gen))); std::cout << "task " << i << " end" << std::endl; return i * i; }) ); } for (auto&& result : results) std::cout << result.get() << ' '; std::cout << std::endl; return 0; } ``` 上面的代码创建了一个包含4个线程线程池,并向线程池中添加了8个任务。每个任务会随机休眠1~100秒的时间,然后返回任务编号的平方。最后打印所有任务的返回值。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值