咳咳。c++11 增加了线程库,从此告别了标准库不支持并发的历史。

然而 c++ 对于多线程的支持还是比較低级,略微高级一点的使用方法都须要自己去实现,譬如线程池、信号量等。

线程池(thread pool)这个东西。在面试上多次被问到,一般的回答都是:“管理一个任务队列。一个线程队列,然后每次取一个任务分配给一个线程去做。循环往复。” 貌似没有问题吧。

可是写起程序来的时候就出问题了。

废话不多说,先上实现。然后再啰嗦。(dont talk, show me ur code !)


  1. #ifndef ILOVERS_THREAD_POOL_H
  2. #define ILOVERS_THREAD_POOL_H
  3. #include <iostream>
  4. #include <functional>
  5. #include <thread>
  6. #include <condition_variable>
  7. #include <future>
  8. #include <atomic>
  9. #include <vector>
  10. #include <queue>
  11. // 命名空间
  12. namespace ilovers {
  13. class TaskExecutor;
  14. }
  15. class ilovers::TaskExecutor{
  16. using Task = std::function<void()>;
  17. private:
  18. // 线程池
  19. std::vector<std::thread> pool;
  20. // 任务队列
  21. std::queue<Task> tasks;
  22. // 同步
  23. std::mutex m_task;
  24. std::condition_variable cv_task;
  25. // 是否关闭提交
  26. std::atomic<bool> stop;
  27. public:
  28. // 构造
  29. TaskExecutor(size_t size = 4): stop {false}{
  30. size = size < 1 ? 1 : size;
  31. for(size_t i = 0; i< size; ++i){
  32. pool.emplace_back(&TaskExecutor::schedual, this); // push_back(std::thread{...})
  33. }
  34. }
  35. // 析构
  36. ~TaskExecutor(){
  37. for(std::thread& thread : pool){
  38. thread.detach(); // 让线程“自生自灭”
  39. //thread.join(); // 等待任务结束。 前提:线程一定会运行完
  40. }
  41. }
  42. // 停止任务提交
  43. void shutdown(){
  44. this->stop.store(true);
  45. }
  46. // 重新启动任务提交
  47. void restart(){
  48. this->stop.store(false);
  49. }
  50. // 提交一个任务
  51. template<class F, class... Args>
  52. auto commit(F&& f, Args&&... args) ->std::future<decltype(f(args...))> {
  53. if(stop.load()){ // stop == true ??
  54. throw std::runtime_error("task executor have closed commit.");
  55. }
  56. using ResType = decltype(f(args...)); // typename std::result_of<F(Args...)>::type, 函数 f 的返回值类型
  57. auto task = std::make_shared<std::packaged_task<ResType()>>(
  58. std::bind(std::forward<F>(f), std::forward<Args>(args)...)
  59. ); // wtf !
  60. { // 加入任务到队列
  61. std::lock_guard<std::mutex> lock {m_task};
  62. tasks.emplace([task](){ // push(Task{...})
  63. (*task)();
  64. });
  65. }
  66. cv_task.notify_all(); // 唤醒线程运行
  67. std::future<ResType> future = task->get_future();
  68. return future;
  69. }
  70. private:
  71. // 获取一个待运行的 task
  72. Task get_one_task(){
  73. std::unique_lock<std::mutex> lock {m_task};
  74. cv_task.wait(lock, [this](){ return !tasks.empty(); }); // wait 直到有 task
  75. Task task {std::move(tasks.front())}; // 取一个 task
  76. tasks.pop();
  77. return task;
  78. }
  79. // 任务调度
  80. void schedual(){
  81. while(true){
  82. if(Task task = get_one_task()){
  83. task(); //
  84. }else{
  85. // return; // done
  86. }
  87. }
  88. }
  89. };
  90. #endif
  91. void f()
  92. {
  93. std::cout << "hello, f !" << std::endl;
  94. }
  95. struct G{
  96. int operator()(){
  97. std::cout << "hello, g !" << std::endl;
  98. return 42;
  99. }
  100. };
  101. int main()
  102. try{
  103. ilovers::TaskExecutor executor {10};
  104. std::future<void> ff = executor.commit(f);
  105. std::future<int> fg = executor.commit(G{});
  106. std::future<std::string> fh = executor.commit([]()->std::string { std::cout << "hello, h !" << std::endl; return "hello,fh !";});
  107. executor.shutdown();
  108. ff.get();
  109. std::cout << fg.get() << " " << fh.get() << std::endl;
  110. std::this_thread::sleep_for(std::chrono::seconds(5));
  111. executor.restart(); // 重新启动任务
  112. executor.commit(f).get(); //
  113. std::cout << "end..." << std::endl;
  114. return 0;
  115. }catch(std::exception& e){
  116. std::cout << "some unhappy happened... " << e.what() << std::endl;
  117. }

为了避嫌,先进行一下版权说明:代码是 me “写”的,可是思路来自 Internet。 特别是这个线程池实现(窝的实现。基本 copy 了这个实现,好东西值得
copy !)。

实现原理

接着前面的废话说。“管理一个任务队列,一个线程队列,然后每次取一个任务分配给一个线程去做,循环往复。

” 这个思路有神马问题?线程池一般要复用线程。所以假设是取一个 task 分配给某一个 thread,运行完之后再又一次分配,在语言层面基本都是不支持的:一般语言的 thread 都是运行一个固定的 task 函数,运行完成线程也就结束了(至少 c++ 是这样)。so 要怎样实现 task 和 thread 的分配呢?

让每个 thread 都去运行调度函数:循环获取一个 task,然后运行之。

idea 是不是非常赞!

保证了 thread 函数的唯一性,并且复用线程运行 task 。

即使理解了 idea,me 想代码还是须要详解一下的。

  1. 一个线程 pool,一个任务队列 queue 。应该没有意见;
  2. 任务队列是典型的生产者-消费者模型,本模型至少须要两个工具:一个 mutex + 一个条件变量。或是一个 mutex + 一个信号量。mutex 实际上就是锁。保证任务的加入和移除(获取)的相互排斥性,一个条件变量是保证获取 task 的同步性:一个 empty 的队列。线程应该等待(堵塞);
  3. stop 控制任务提交,是受了 Java 的影响,还有实现类不叫 ThreadPool 而是叫 TaskExecutor;
  4. atomic<bool> 本身是原子类型,从名字上就懂:它们的操作 load()/store() 是原子操作,所以不须要再加 mutex。

c++语言细节

即使懂原理也不代表能写出程序。上面用了众多c++11的“奇技淫巧”,以下简单描写叙述之。

  1. using Task = function<void()> 是类型别名,简化了 typedef 的使用方法。function<void()> 能够觉得是一个函数类型,接受随意原型是 void() 的函数。或是函数对象,或是匿名函数。void() 意思是不带參数,没有返回值。

    最初的实现版本号 Task 类型不是单纯的函数类型,而是一个 class,包括一个 status 字段。表明 Task 的状态:未调度、运行中、运行结束。后来由于简化,故删掉了。

  2. pool.emplace_back(&TaskExecutor::schedual, this); 和 pool.push_back(thread{&TaskExecutor::schedual, this}) 功能一样,仅仅只是前者性能会更好;
  3. thread{&TaskExecutor::schedual, this} 是构造了一个线程对象。运行函数是成员函数 TaskExecutor::schedual 。
  4. 全部对象的初始化方式均採用了 {},而不再使用之前的 () 方式,由于风格不够一致且easy出错;
  5. 匿名函数: [](int a, int b)->int { return a+b; } 不多说。[] 是捕捉器,&r 是引用域外的变量 r, =r 是拷贝域外的 r 值;
  6. delctype(expr) 用来判断 expr 的类型。和 auto 是类似的。相当于类型占位符。占领一个类型的位置;auto f(A a, B b) -> decltype(a+b) 是一种使用方法,不能写作 decltype(a+b) f(A a, B b),为啥?!

    c++ 就是这么规定的!

  7. commit 方法是不是略奇葩!

    能够带随意多的參数,第一个參数是 f。后面依次是函数 f 的參数! 可变參数模板是 c++11 的一大亮点,够亮!至于为什么是 Arg... 和 arg... 。由于规定就是这么用的!

  8. make_shared 用来构造 shared_ptr 智能指针。

    使用方法大体是 shared_ptr<int> p = make_shared<int>(4) 然后 *p == 4 。智能指针的优点就是。 自己主动 delete !

  9. bind 函数。接受函数 f 和部分參数,返回currying后的匿名函数。譬如 bind(add, 4) 能够实现类似 add4 的函数!
  10. forward() 函数,类似于 move() 函数,后者是将參数右值化,前者是... 肿么说呢?大概意思就是:不改变最初传入的类型的引用类型(左值还是左值,右值还是右值);
  11. packaged_task 就是任务函数的封装类,通过 get_future 获取 future , 然后通过 future 能够获取函数的返回值(future.get());packaged_task 本身能够像函数一样调用 () ;
  12. queue 是队列类, front() 获取头部元素, pop() 移除头部元素;back() 获取尾部元素,push() 尾部加入元素。
  13. lock_guard 是 mutex 的 stack 封装类,构造的时候 lock(),析构的时候 unlock(),是 c++ RAII 的 idea。
  14. condition_variable cv; 条件变量, 须要配合 unique_lock 使用。unique_lock 相比 lock_guard 的优点是:能够随时 unlock() 和 lock()。 cv.wait() 之前须要持有 mutex,wait 本身会 unlock() mutex,假设条件满足则会又一次持有 mutex。

结束语

是不是感觉有些反人类!

c++11线程池实现的更多相关文章

  1. 托管C++线程锁实现 c++11线程池

    托管C++线程锁实现   最近由于工作需要,开始写托管C++,由于C++11中的mutex,和future等类,托管C++不让调用(报错),所以自己实现了托管C++的线程锁. 该类可确保当一个线程位于 ...

  2. 简单的C++11线程池实现

    线程池的C++11简单实现,源代码来自Github上作者progschj,地址为:A simple C++11 Thread Pool implementation,具体博客可以参见Jakob's D ...

  3. c++11 线程池学习笔记 (一) 任务队列

    学习内容来自一下地址 http://www.cnblogs.com/qicosmos/p/4772486.html github https://github.com/qicosmos/cosmos ...

  4. C++11线程池的实现

    什么是线程池 处理大量并发任务,一个请求一个线程来处理请求任务,大量的线程创建和销毁将过多的消耗系统资源,还增加了线程上下文切换开销. 线程池通过在系统中预先创建一定数量的线程,当任务请求到来时从线程 ...

  5. c++ 11 线程池---完全使用c++ 11新特性

    前言: 目前网上的c++线程池资源多是使用老版本或者使用系统接口实现,使用c++ 11新特性的不多,最近研究了一下,实现一个简单版本,可实现任意任意参数函数的调用以及获得返回值. 0 前置知识 首先介 ...

  6. 基于C++11线程池

    1.包装线程对象 class task : public std::tr1::enable_shared_from_this<task> { public: task():exit_(fa ...

  7. 《java.util.concurrent 包源码阅读》11 线程池系列之ThreadPoolExecutor 第一部分

    先来看ThreadPoolExecutor的execute方法,这个方法能体现出一个Task被加入到线程池之后都发生了什么: public void execute(Runnable command) ...

  8. c++11线程池

    #pragma once #include <future> #include <vector> #include <atomic> #include <qu ...

  9. c++11 线程池

    也可参考: https://www.cnblogs.com/ailumiyana/p/10016965.html *** https://blog.csdn.net/jlusuoya/article/ ...

随机推荐

  1. Mac 快捷键整理(不定期更新)

    刚用Mac, 感到有点困难,记录几个快捷键: 1) 在全屏间切换: ctrl + command + F 2)向后删: Fn + delete

  2. CocoSourcesCS 2

    CocoSourcesCS 2 /*------------------------------------------------------------------------- DFA.cs - ...

  3. [Functional Programming ADT] Adapt Redux Actions/Reducers for Use with the State ADT

    By using the State ADT to define how our application state transitions over time, we clear up the ne ...

  4. poj 2236 Wireless Network 【并查集】

    Wireless Network Time Limit: 10000MS   Memory Limit: 65536K Total Submissions: 16832   Accepted: 706 ...

  5. WebService 之 协议篇

    Web Service 使用的是 SOAP (Simple Object Access Protocol)简单对象访问协议,是交换数据的一种协议规范,是一种轻量的.简单的.基于XML(标准通用标记语言 ...

  6. js中,三元运算的简单应用(?:)

    js中,三元运算的简单应用: var sinOrMul = ""; sinOrMul =(subType=="single")?("<span ...

  7. 正则表达式:日期,电话,邮箱等常用字符串;js中日期的带下的比较,获取不同格式的日期

    一.日期 (1)首先需要验证年份,显然,年份范围为 0001 - 9999,匹配YYYY的正则表达式为: [0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1- ...

  8. Core Data 删除某条指定记录的数据

    一:操作流程 先查询得到某条要删除的数据 然后删除某记录 二:演示代码 //删除 - (void)deleteThePersonData { NSFetchRequest *fetchRequest ...

  9. WebDriver API——第2部分Exceptions

    Exceptions that may happen in all the webdriver code. exception selenium.common.exceptions.ElementNo ...

  10. eslint for...in 报错处理

    示例代码: <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF ...