c++11实现线程池

  原文地址:http://blog.csdn.net/zdarks/article/details/46994607

  线程池的实现原理很简单:就是管理一个任务队列,一个线程队列,然后每次取一个任务分配给一个线程去做,循环往复。

1.代码

  1 #include <thread>
  2 #include <mutex>
  3 #include <future>
  4 #include <atomic>
  5 #include <condition_variable>
  6 #include <functional>
  7 #include <vector>
  8 #include <queue>
  9 #include <memory>
 10 #include <iostream>
 11 #include <string>
 12 using namespace std;
 13 
 14 class TaskExecutor
 15 {
 16     using Task = function<void()>;
 17 private:
 18     //线程池
 19     vector<thread> pool;
 20     //任务队列
 21     queue<Task> tasks;
 22     //同步
 23     mutex mtx;
 24     condition_variable cv;
 25     //是否关闭提交
 26     atomic<bool> isStop;
 27 public:
 28     TaskExecutor(int size = 4):isStop(false)
 29     {
 30         if (size < 1)
 31             size = 1;
 32         for (int i = 0; i < size; ++i)
 33         {
 34             pool.emplace_back(&TaskExecutor::schedual, this);
 35         }
 36     }
 37 
 38     ~TaskExecutor()
 39     {
 40         for (auto& t : pool)
 41             t.join();
 42     }
 43 
 44     //停止任务提交
 45     void shutdown()
 46     {
 47         this->isStop.store(true);
 48     }
 49 
 50     //重启任务提交
 51     void restart()
 52     {
 53         this->isStop.store(false);
 54     }
 55 
 56     //提交一个任务
 57     template <class F, class... Args>
 58     auto commit(F&& f, Args&&...args)->future<decltype(f(args...))>
 59     {
 60         if (isStop.load())
 61         {
 62             throw std::runtime_error("task executor have closed commit.");
 63         }
 64         using ResType = decltype(f(args...));
 65         auto task = make_shared<packaged_task<ResType()>>(
 66             std::bind(forward<F>(f), forward<Args>(args)...)
 67             );
 68         //添加任务到队列
 69         {
 70             lock_guard<mutex> lck(mtx);
 71             tasks.emplace([task]() {
 72                 (*task)();
 73             });
 74         }
 75         cv.notify_one();
 76         future<ResType> future = task->get_future();
 77         return future;
 78     }
 79 
 80     
 81 private:
 82     //获得一个待执行的Task
 83     Task get_one_task()
 84     {
 85         unique_lock<mutex> lck(mtx);
 86         cv.wait(lck, [&]() {
 87             return !tasks.empty();
 88         });
 89         Task task(move(tasks.front()));
 90         tasks.pop();
 91         return task;
 92     }
 93     //任务调度
 94     void schedual()
 95     {
 96         while (true)
 97         {
 98             if (Task task = get_one_task())
 99             {
100                 task();
101             }
102             else
103             { }
104         }
105     }
106 };
107 
108 
109 void f()
110 {
111     cout << "hello f" << endl;
112 }
113 
114 struct G
115 {
116     int operator()()
117     {
118         cout << "hello g!" << endl;
119         return  42;
120     }
121 };
122 mutex m;
123 void x(int i)
124 {
125     unique_lock<mutex> lck(m);
126     cout << "" << i << "号线程被调度" << endl;
127 }
128 
129 int main()
130 {
131     TaskExecutor executor(10);
132     future<void> ff = executor.commit(f);
133     future<int> fg = executor.commit(G());
134     future<string> fh = executor.commit([]()->string {
135         cout << "hello h!" << endl;
136         return "hello,fh!";
137     });
138     executor.shutdown();
139     ff.get();
140     cout << fg.get() << " " << fh.get() << endl;
141 
142     this_thread::sleep_for(chrono::seconds(5));
143     executor.restart();
144     executor.commit(f).get();
145 
146     cout << "end..." << endl;
147     
148 
149     for (int i = 0; i < 100; ++i)
150     {
151         executor.commit(x, i);
152     }
153     
154 }

 

2.实现原理  

  “管理一个任务队列,一个线程队列,然后每次取一个任务分配给一个线程去做,循环往复。” 这个思路有神马问题?线程池一般要复用线程,所以如果是取一个 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。

3.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. 所有对象的初始化方式均采用了 {},而不再使用之前的 () 方式,因为风格不够一致且容易出错;
  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。
posted @ 2016-09-17 19:14  zhangbaochong  阅读(856)  评论(0)    收藏  举报