简单的线程池(七)

◆ 概要

本文中,笔者为 《简单的线程池(四)》 提及的非阻塞独占式线程池增加了一项功能:当某个工作线程的任务队列中无工作任务时,此工作线程可以去其他工作线程的任务队列中获取任务。笔者称之为非阻塞互助式线程池。

gist

笔者对比了测试结果与 《简单的线程池(六)》 的数据,得出了添加功能前后的差异。

本文不再赘诉与 《简单的线程池(四)》 相同的内容。如有不明之处,请参考该博客。

◆ 实现

以下代码给出了此线程池的实现,(lockwise_mutual_pool.h)

class Thread_Pool {

  private:

    struct Task_Wrapper { ...
    
    };

    atomic<bool> _suspend_;
    atomic<bool> _done_;
    unsigned _workersize_;
    thread* _workers_;
    Lockwise_Queue<Task_Wrapper>* _workerqueues_;

    void work(unsigned index) {
        Task_Wrapper task;
        while (!_done_.load(memory_order_acquire)) {
            if (_workerqueues_[index].pop(task))
                task();
            else                                                     // # 1
                for (unsigned i = 0; i < _workersize_; ++i)
                    if (_workerqueues_[(index + i + 1) % _workersize_].pop(task)) {    // #2
                        task();
                        break;
                    }
            while (_suspend_.load(memory_order_acquire))
                std::this_thread::yield();
        }
    }

    void stop() { ...

    }

  public:
    Thread_Pool() : _suspend_(false), _done_(false) { ....

    }
    ~Thread_Pool() { ...
    
    }

    template<class Callable>
    future<typename std::result_of<Callable()>::type> submit(Callable c) { ...
    
    }

};

此线程池的代码与 非阻塞独占式线程池 相比,仅在工作线程的初始函数 Thread_Pool::work(unsigned) 上有区别。当某个工作线程的任务队列中无工作任务时(#1),此工作线程会去其他工作线程的任务队列中获取任务。这里涉及到的“选择其他工作线程的任务队列的算法”(#2),来自于本文最后的参考书籍。

◆ 逻辑

此线程池的结构和与 《简单的线程池(四)》 中的一致,此处略。

此线程池用户提交任务与工作线程执行任务的并发过程与 《简单的线程池(一)》 中的一致,此处略。

◆ 验证

验证过程采用了 《简单的线程池(三)》 中定义的的测试用例。笔者对比了测试结果与 《简单的线程池(六)》 的数据,结果如下,

图1 列举了 吞吐量1的差异 在 0.5 分钟、1 分钟和 3 分钟的提交周期内不同思考时间上的对比。

图1

【注】非阻塞互助式 略称为 LM ,下同。

可以看到,

  • 当思考时间为 0 时,LM 的吞吐量优于 LS、BS 的吞吐量,相当于 LU、BU 的吞吐量;延长提交周期后,LM 和 LS、BS、LU、BU 的吞吐量差异没有发生明显变化;
  • 当思考时间不为 0 时,LM 的吞吐量大幅优于 LS、LU 的吞吐量,大幅劣于 BS、BU 的吞吐量,但差异不会因提交周期的延长而大幅变化;随着思考时间的增加,LM 的吞吐量与 LS、BS、LU、BU 的吞吐量差异逐渐消失。

图2 列举了 吞吐量2的差异 在 0.5 分钟、1 分钟和 3 分钟的提交周期内不同思考时间上的对比。

图2

可以看到,

  • 当思考时间为 0 时,LM 的吞吐量优于 LS、BS、LU、BU 的吞吐量;延长提交周期后,LM 和 LS、BS、LU、BU 的吞吐量差异没有发生明显变化;
  • 当思考时间不为 0 时,因 LM 和 LS、BS、LU、BU 的吞吐量均为 0,它们间没有差异。

图3 列举了 吞吐量3的差异 在 0.5 分钟、1 分钟和 3 分钟的提交周期内不同思考时间上的对比。

图3

可以看到,

  • 当思考时间为 0 时,LM 的吞吐量相当于 LS、BS、LU 的吞吐量,略劣于 BU 的吞吐量;延长提交周期后,LM 和 LS、BS、LU、BU 的吞吐量差异没有发生明显变化;
  • 当思考时间不为 0 时,LM 的吞吐量大幅优于 LS、LU 的吞吐量,大幅劣于 BS、BU 的吞吐量,但差异不会因提交周期的延长而大幅变化;随着思考时间的增加,LM 的吞吐量与 LS、BS、LU、BU 的吞吐量差异逐渐消失。

基于以上的对比分析,笔者认为,在非阻塞式线程池中,

  • 互助式的吞吐量指标优于共享式、独占式的吞吐量指标。

◆ 最后

完整的示例代码和测试结果请参考 [github] cnblogs/15710614

关于“选择其他工作线程的任务队列的算法”,笔者参考了 C++并发编程实战 / (美)威廉姆斯 (Williams, A.) 著; 周全等译. – 北京: 人民邮电出版社, 2015.6 (2016.4重印) 一书。致 Anthony Williams、周全等译者。