线程池C++11实现

##线程池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表达

位置([任务](){(*任务)(); }); `

这里将任务这个可调用对象拷贝到匿名函数对象里面,封装起来写进进任务变量里。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值