##线程池C ++ 11实现
原始码[https://github.com/TarsCloud/TarsCpp/tree/29b0107ce979e974c05f28210b32b1ab4d781113](https://github.com/TarsCloud/TarsCpp/tree/29b0107ce979e974c05f28210b32b1ab4d781113)在以前的地方有很多但是细节方面稍微和偏向于ç语言的版本有点有点不一样。
/ ** * @file tc_thread_pool.h * @brief线程池类,采用c ++ 11来实现了*使用说明:* TC_ThreadPool tpool; * tpool.init(5); //初始化线程池线程数* //启动线程有两种方式* //第一种,直接启动* tpool.start(); * //第二种,启动时指定初始化函数,例如定义函数* void testFunction(int i)* {* cout << i << endl; *} * tpool.start(testFunction,5); // //开始的第一函数是std :: bind返回的函数(std :: function),后面跟参数* //将任务丢到线程池中* tpool.exec(testFunction,10); //参数和开始相同* //等待线程池结束,有两种方式:* //第一种等待线程池中无任务*tpool。waitForAllDone(1000); //参数<0时,表示无限等待(注意有人调用) stop也会推出)* //第二种等待外部有人调用线程池的stop函数*水池。waitForStop(1000); *
//此时:外部需要结束线程池是调用* tpool。停止(); *注意:* TC_ThreadPool :: EXEC执行任务返回的是一个未来,因此可以通过将来逐步获取结果,例如:* INT testInt(INT我)* {*返回我;执行(testInt,5); *} *自动f = tpool。* cout << f。得到()<< endl; //当testInt在线程池中执行后,f.get()会返回数值5 * * class Test * {* public:* int test(int i); *}; *测试t; * auto f =滑车。EXEC(STD ::绑定(&测试::测试,&吨,的std ::占位符:: _1),10); * //返回的未来对象,可以检查是否执行* cout << f。得到()<< endl; * @作者jarodruan @upchina。com * / / // // ** * @brief线程异常* / struct TC_ThreadPool_Exception:公共TC_Exception {TC_ThreadPool_Exception(常量变量和异常):TC_Exception(buffer){}; TC_ThreadPool_Exception(常量字符串和二进制,布尔错误):TC_Exception(帧,错误,错误){}; ; }; / * * * @brief用通线程池类(采用C ++ 11实现)* *使用方式说明:*具体示例请参见:实例/ UTIL / example_tc_thread_pool.cpp * /类TC_ThreadPool { 公共:
/ * * * @brief构造函数 * * / TC_ThreadPool();/ * * * @brief析构,会停止所有线程 * /〜TC_ThreadPool();/ * * * @brief初始化。* * @param num工作线程一个数* / void init(size_t num);/ * * * @brief获取线程个数。 * * @返回为size_t线程个数 * / 的size_t getThreadNum(){ STD :: unique_lock < STD ::互斥>
锁(_mutex);返回_threads。大小(); } / * * * @brief获取当前线程池的任务数* * @返回的size_t线程池的任务数* / 的size_t getJobNum(){ STD :: unique_lock < STD ::互斥>锁(_mutex); return_tasks。大小(); } / * * * @brief停止所有线程,会等待所有线程结束 * / void stop();/ * * * @brief启动所有线程 * / void start();/ * * * @brief用线程池启用任务(F是函数,参数数量是参数) * * @参数ParentFunctor * @参数TF * @返回返回任务的未来对象,可以通过这个对象来获取返回值 * / 模板< ˚F级,B级。。。参数数量>自动执行(˚F && ˚F,参数数量&&。。。ARGS)- >
STD ::未来< decltype (˚F ( ARGS 。。。))> { //定义返回值类型 使用RetType = decltype (˚F ( ARGS 。。。)); 自动任务=的std :: make_shared < STD :: packaged_task < RetType ()>> ( STD ::绑定( STD :
::向前< F > ( f ),标准::向前<参数数量>(ARGS。。。。)); STD ::将来< RetType > RES =任务- > get_future(); STD :: unique_lock < STD ::互斥>锁(_mutex); _tasks位置([任务](){ (*任务)(); } ); _健康)状况
。notify_one ();返回res ; } / * * * @brief等待当前任务主体中,所有工作全部结束(副本无任务)。 * * @参数millsecond等待的时间(毫秒),- 1:永远等待 * @返回真,所有工作都处理完毕 *假,超时退出 * / 布尔waitForAllDone(INT millsecond = - 1); 保护:/ * * * @brief获取任务 * * @返回的STD ::函数<空隙()> * / 布尔格(STD ::功能<空隙()> &任务);/ * * * @brief线程池是否退出 * / bool isTerminate(){ return _bTerminate ; } / * * * @brief线程运行态 * / void run();保护:/ * * *任务类别 * / 本身< STD ::功能<空隙()>> _tasks ; / * * * 工作线程* / STD ::向量< STD ::线程 * > _threads ; STD ::互斥_mutex ; STD :: condition_variable_condition; size_t _threadNum ; bool _bTerminate ; 标准:原子
< int > _atomic { 0 } ; } ; }#ENDIF
/***********************tc_thread_pool.h******************** ***** / /***********************tc_thread_pool.cpp***************** ******** / / ** *腾讯很高兴通过提供Tars支持开源社区。* *腾讯公司(C)2016THL A29 Limited版权所有。版权所有。* *根据BSD 3条款许可(“许可”)许可;除*许可外,您不得使用该文件。您可以在*处获得许可的副本
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
#include "util/tc_thread_pool.h"
#include "util/tc_common.h"
#include <iostream>
namespace tars
{
TC_ThreadPool::TC_ThreadPool()
: _threadNum(1), _bTerminate(false)
{
}
TC_ThreadPool::~TC_ThreadPool()
{
stop();
}
void TC_ThreadPool::init(size_t num)
{
std::unique_lock<std::mutex> lock(_mutex);
if (!_threads.empty())
{
throw TC_ThreadPool_Exception("[TC_ThreadPool::init] thread pool has start!");
}
_threadNum = num;
}
void TC_ThreadPool::stop()
{
{
std::unique_lock<std::mutex> lock(_mutex);
_bTerminate = true;
_condition.notify_all();
}
for (size_t i = 0; i < _threads.size(); i++)
{
if(_threads[i]->joinable())
{
_threads[i]->join();
}
delete _threads[i];
_threads[i] = NULL;
}
std:: unique_lock < STD ::互斥> 锁( _mutex );
_threads 。清除(); }空隙TC_ThreadPool ::开始(){ STD :: unique_lock < STD ::互斥>锁( _mutex ); 如果(! _threads 。空()){
throw TC_ThreadPool_Exception("[TC_ThreadPool::start] thread pool has start!");
}
for (size_t i = 0; i < _threadNum; i++)
{
_threads.push_back(new thread(&TC_ThreadPool::run, this));
}
}
bool TC_ThreadPool::get(std::function<void()> &task)
{
std::unique_lock<std::mutex> lock(_mutex);
if (_tasks.empty())
{
_condition.wait(lock, [this] { return _bTerminate || !_tasks.empty(); });
}
if (_bTerminate)
return false;
if (!_tasks.empty())
{
task = std::move(_tasks.front());
_tasks.pop();
return true;
}
return false;
}
void TC_ThreadPool::run()
{
//调用处理部分
while (!isTerminate())
{
std::function<void()> task;
bool ok = get(task);
if (ok)
{
++_atomic;
try
{
task();
}
catch (...)
{
}
--_atomic;
//任务都执行完毕了
std::unique_lock<std::mutex> lock(_mutex);
if (_atomic == 0 && _tasks.empty())
{
_condition.notify_all();
}
}
}
}
bool TC_ThreadPool::waitForAllDone(int millsecond)
{
std::unique_lock<std::mutex> lock(_mutex);
if (_tasks.empty())
return true;
if (millsecond < 0)
{
_condition.wait(lock, [this] { return _tasks.empty(); });
return true;
}
else
{
return _condition.wait_for(lock, std::chrono::milliseconds(millsecond), [this] { return _tasks.empty(); });
}
}
}
/***********************tc_thread_pool.cpp*************************/
###分析###参数和普通线程池的类一样它的成员变量都有着这几个参数
1.任务类别
queue<std::function<void()>> _tasks;
它是用来存放我们需要运行的任务的,简单的说也就是我们需要线程里调用的函数,以前的线程池一般会在任务队列里面再添加上userdata也就是函数对应需要处理的数据,但是它这里做了一些c++特性的封装,不需要特别存储userdata,稍后介绍exec的函数会讲到。
2. 线程安全以及线程等待
std::mutex _mutex;
std::condition_variable _condition;
这里是老生常谈了,我们需要一个互斥锁来保护调用我们共享数据的线程安全,以及需要一个条件变量来让没有工作可做的线程可以休眠,有工作来的时候就唤醒它。
3. 线程管理
std::vector<std::thread*> _threads;
size_t _threadNum;
std::atomic<int> _atomic{0};
主要用来管理工作线程以及其数量。这里还有加上一个原子类型的计数器来记录正在工作的线程。
4. 关闭管理
bool _bTerminate;
线程池一般都会创建一个Terminate变量来记录该线程池是否需要关闭。
函数调用
整个线程池的模型还是很传统的,就是用了一个任务队列来保存任务,使用的是非常普遍的生产者-消费者模型。
这里的init函数只是初始化线程的数量,真正创建以及初始化线程是在start的时候来初始化的,它绑定了一个run函数,run函数是对工作线程的封装函数,它包括了判断线程是否还处于工作状态,判断任务队列是否空。然后从任务队列中获取已经封装好的可调用函数(也叫做任务)出来运行,如果没有则通过调用条件变量的wait函数进入等待的状态。
然后调用exec函数往队列里面添加任务,这个地方用的新特性很多,这里展开一下。
c++可变参数模版
template <class F, class... Args>
它是c++里新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数。相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以它也是C++11中最难理解和掌握的特性之一。虽然掌握可变模版参数有一定难度,但是它却是C++11中最有意思的一个特性。
由于这里需要传递函数(任务)进来,所以肯定会带上形参,而且由于函数的形参个数是不确定的,要附带的参数类型也都是都是可变的,所以我们需要用到可变模版参数函数。
返回值后置
auto exec(F &&f, Args &&... args) -> std::future<decltype(f(args...))>
我们一般的时候使用的函数定义方式都叫前置返回类型的
int func(int a, int b){
}
而后置返回类型就是把返回类型写在参数传递之后,用到情况比较多的是类的成员函数返回类型非常的复杂的时候,我们可以把函数返回类型放在函数传参的后面。
简单例子:
auto func123 -> int; // 声明
auto func123 -> int { // 定义
}
还有一种情况就是这里,需要用到decltype来判断传参的返回类型(decltype后面有说),这里需要结合auto字段。
结合decltype的情况:
auto FuncTmp(T &tv) -> decltype (func(tv) )
{
returnt tv;
}
注意这里的func虽然是一个函数,但是并不会真的去调用,而只是去判断它的返回类型。
还有decltype只能放在后置,因为像里面的f和args这些参数都是在定义了exec(F &&f, Args &&… args)之后才有初始化,如果像这样把decltype放在前面来判断类型会报错。
std::future<decltype(f(args...))> exec(F &&f, Args &&... args) // ✖
decltype类型推导
using RetType = decltype(f(args...));
decltype用于推导类型,主要作用 : 返回操作数的操作类型。
(a) decltype的自动类型推断会发生在编译器(和auto 一样) 如果参数里传的是一个函数(类似func()),它也不会执行。
(b) decltype不会真正计算表达式的值,例如decltype(i+1)。
它推导类型的时候不像auto一样会把const 和 引用属性等给忽略掉而是全部照搬。
const int i = 0;
const int &iy = i;
auto j1 = i; // j1 = int; const 和 & 属性会抛弃掉
decltype(i) j2 = 15; // j2 = const int, 把所有的变量属性都返回
decltype(iy) j3 = j2; // j3 = const int &, 如果decltype中是个变量,变量的const属性,以及引用属性&都会被返回。
这里主要是用作来推导函数(任务)的返回类型,然后将其放入future里面,方便用户可以从future里面get到返回的数据。
完美转发、packaged_task、make_shared
auto task = std::make_shared<std::packaged_task<RetType()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
packaged_task至少是std :: funciton,它是绑定绑定可调用对象,然后执行,但是它实际上是相当于把一个可调用对象链接到了未来,我们需要用future来获取它的返回数据,用于多make_shared是智能指针,可以自动删除。线程初始化。完美转发使用来转发类型的,防止在传递参数的时候会有类型右值变左值的情况出现。
Lambda表达
位置([任务](){(*任务)(); }); `
这里将任务这个可调用对象拷贝到匿名函数对象里面,封装起来写进进任务变量里。