走进C++11(三十一) 如何用60行实现C++11 thread pool

关注公众号获取更多信息:

 

开始之前先上一下代码链接:

 

https://github.com/maxcong001/threadPool

 

最近看了看我的计划,写道这里也算是到了一半,大部分都是讲的单一的C++11的用法,基本都是理论知识,就像我上大学的时候,老师一直讲理论知识,结局就是能去能不去的时候,我选择了后者。所以在这里穿插一下小的综合运用文章,让大家知道为什么要用C++11,C++11好在哪里,项目中如何运用C++11.

 

首先介绍一下背景。在我们的工作中,避免不了多线程之间的配合。在现在处理器动辄8核16核的背景下,如果我们的程序还停留在单线程的模型,那么我们就没法享受多处理器带来的性能提成。之前看过我司代码中的threadpool。写的那叫一个滴水不漏,每个小细节都有大量的代码去实现。不但非常冗长,而且以我的智商基本上读不懂。唯一的有点就是:真稳定。不过threadpool的模型已经很难融入现代C++了。

 

所以有必要通过C++11来重新实现一下threadpool,对比一下modern

C++和C98.

 

1. 为什么要有threadpool?

 

如果谈论threadpool,你会想到有什么功能呢?

 

传统的模型大概是这样的,把一个函数指针传给threadpool。然后thread们会在合适的时候调用这个函数。那么还有一个问题就是函数的返回值怎么传递回调用的线程。这个功能往往有很多种方法,我司的思路就是调用你的callback将函数返回值返回给你。当然不是返回给调用函数的线程。

 

以上的描述中反映的threadpool的两个最基本的需求:

 

  1.  可以把一个可执行的对象扔给threadpool去执行。

  2. 可以把执行的返回值带回。

 

其实这就是threadpool存在的合理性-- 把工作扔给它,我只要知道结果就行。当然任务扔给threadpool后,你就可以去干一些别的工作。

 

有人会说,扔给threadpool,无非是让别的线程去干活,干的总活并没有减少。相反,一些threadpool的开销反而让工作变的更慢。至于这个问题我想用redis来举例子。

 

众所周知,redis最新版本支持的多线程。redis的作者在解释为什么引入多线程的时候说过。在他们维护redis的时候,发现redis的瓶颈竟然出现在分配内存上(从socket上拷贝内存)。所以你会发现redis起了多线,只是为了加速内存拷贝,最终的逻辑还是在一个线程执行的。所以可以看出,可以把较慢的代码或者可以流水操作的代码让不同的线程执行。

 

2. 现代化threadpool提出了什么更高的要求?

 

之前我们分享过std::function。std::function 是C++11提供的可执行代码的包装器,它可以是一个普通函数,或者是函数指针,或者是lambda...,所以对于我们来说,threadpool也要支持std::function能支持的类型。

 

关于返回值,还有如何返回到calling thread,之前我们也分享过std::future.

 

如果大家忘记了这两个概念,可以回去找找之前的文章复习一下。

走进C++11(二十三) 函数对象包装器之std::function

走进C++11(二十七) 处理未来发生的事情 std::future

 

还有就是线程间的同步,之前我们分享过 std::condition_variable,如果忘记了,看看这个吧:

走进C++11(三十)标准化条件变量 -- condition_variable

 

还有就是thread的包装器,我们用了std::thread,同样,如果记不清了,看看这个:

走进C++11(二十四)一统江湖之线程 -- std::thread

 

 

至此我们凑齐了实现threadpool的几大件,下面我们看看如何来实现它

3. 原理:

3.1 对象定义

要实现一个threadpool。我们要有以下的信息:

 

  1. 我们要有个结构体,记住我们控制的thread。

  2. 我们要有个结构体,记住我们要做的事情。

  3. 我们要有个condition_variable来做线程间同步。

  4. 为了优雅的推出,我们要有个标志位,标志着我现在想推出了,大家都退下吧。

     

功能上要有:

  1. 构造函数

  2. 析构函数

  3. 最重要的 -- 添加任务的函数

 

实现起来如下:

 

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;};

 

 

3.2 初始化

 

这里构建了我们需要的thread。并把它放在一个vector里。

 

这个thread只干一件事,那就是等condition_variable的通知,如果有通知,那么从task queue里边拿出一个task,并执行该task。

 

当然还有一些判断是否退出的逻辑。

 

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();                }            });}

 

4. 添加任务API

 

说到函数,不可避免的就是函数的实现和函数的参数,为了实现支持不同的类型,我们选择了用模板来适配。

 

同时为了得到返回值,我们将返回值设置成了future。那么问题来了,这该如何实现?是不是想起了packaged_task? 如果忘了,回忆一下吧。

走进C++11(二十九) 将工作打包成任务,丢给执行者 -- std::packaged_task

 

packaged_task可以将可执行的工作打包,然后获取它的future。

 

至此我们就可以实现我们的功能了。思路就是来了一个可执行的工作,首先封装成packaged_task。然后把这个task放到task queue中。并且通知一个线程说queue里边有东西了,赶紧去干活。

 

在返回之前,得到它的future并返回。

 

实现如下:

 

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;}

 

至此,所有功能都实现了,有了C++11,是不是一切都变得美好了起来,用了60行就实现了以前无数行才能实现的功能,而且简单易懂,支持现代化的C++调用。

 

5. 完整代码

 

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:    std::vector<std::thread> workers;    std::queue<std::function<void()>> tasks;    std::mutex queue_mutex;    std::condition_variable condition;    bool stop;};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;}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 ThreadPool::~ThreadPool(){    {        std::unique_lock<std::mutex> lock(queue_mutex);        stop = true;    }    condition.notify_all();    for (std::thread &worker : workers)        worker.join();}

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值