0


【Linux】线程池的简易实现(懒汉模式)

文章目录


前言

`

线程池:
一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

  • 线程池的应用场景:
    1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技 术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个 Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
    1. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
    1. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情 况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限, 出现错误.

一、懒汉方式

1.普通模式

懒汉方式实现单例模式

template<typenameT>classSingleton{static T* inst;public:static T*GetInstance(){if(inst ==NULL){
inst =newT();}return inst;}};

1.线程安全模式

存在一个严重的问题, 线程不安全.
第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例.
但是后续再次调用, 就没有问题了.

// 懒汉模式, 线程安全
template <typename T>
class Singleton {volatilestatic T* inst;// 需要设置 volatile 关键字, 否则可能被编译器优化.static std::mutex lock;
public:static T*GetInstance(){if(inst ==NULL){// 双重判定空指针, 降低锁冲突的概率, 提高性能.
lock.lock();// 使用互斥锁, 保证多线程情况下也只调用一次 new.if(inst ==NULL){
inst = new T();}
lock.unlock();}return inst;}};

注意事项:1. 加锁解锁的位置
2. 双重 if 判定, 避免不必要的锁竞争
3.volatile关键字防止过度优化

二、源代码

1.Task.hpp(要执行的任务)

可根据自己的需求修改要执行的任务,这里我们以进行两个数的加减乘除为例子

#pragmaonce#include<iostream>#include<string>
std::string opers ="+-*/%";//运算符号enum//返回值{
    DivZero =1,
    ModZero,
    Unknown
};classTask{public:Task(){}Task(int x,int y,char op):data1_(x),data2_(y),oper_(op),result_(0),exitcode_(0){}voidrun(){switch(oper_){case'+':
            result_ = data1_ + data2_;break;case'-':
            result_ = data1_ - data2_;break;case'*':
            result_ = data1_ * data2_;break;case'/':{if(data2_ ==0)
                exitcode_ = DivZero;else
                result_ = data1_ / data2_;}break;case'%':{if(data2_ ==0)
                exitcode_ = ModZero;else
                result_ = data1_ % data2_;}break;default:
            exitcode_ = Unknown;break;}}voidoperator()()//直接使用仿函数,调用run方法{run();}

    std::string GetResult(){
        std::string r = std::to_string(data1_);
        r += oper_;
        r += std::to_string(data2_);
        r +="=";
        r += std::to_string(result_);
        r +="[code: ";
        r += std::to_string(exitcode_);
        r +="]";return r;}
    std::string GetTask(){
        std::string r = std::to_string(data1_);
        r += oper_;
        r += std::to_string(data2_);
        r +="=?";return r;}~Task(){}private:int data1_;int data2_;char oper_;int result_;int exitcode_;};

2.ThreadPool.hpp(线程池)

#pragmaonce#include<iostream>#include<vector>#include<string>#include<queue>#include<pthread.h>#include<unistd.h>structThreadInfo//线程信息{
    pthread_t tid;
    std::string name;};staticconstint defalutnum =5;//默认创建线程个数template<classT>classThreadPool{public:voidLock(){pthread_mutex_lock(&mutex_);}voidUnlock(){pthread_mutex_unlock(&mutex_);}voidWakeup(){pthread_cond_signal(&cond_);}voidThreadSleep(){pthread_cond_wait(&cond_,&mutex_);}boolIsQueueEmpty(){return tasks_.empty();}
    std::string GetThreadName(pthread_t tid){for(constauto&ti : threads_){if(ti.tid == tid)return ti.name;}return"None";}public:staticvoid*HandlerTask(void*args)//这里需要用静态函数,因为在类中默认隐藏一个this指针{//这里传args直接把ThreadPool这个类传入进来进行操作//这样既能访问类中的方法也能符合传入的参数个数为1个的规则
        ThreadPool<T>*tp =static_cast<ThreadPool<T>*>(args);
        std::string name = tp->GetThreadName(pthread_self());while(true){
            tp->Lock();while(tp->IsQueueEmpty())//循环判断,防止线程的伪唤醒{
                tp->ThreadSleep();}
            T t = tp->Pop();
            tp->Unlock();t();//直接调用任务的处理方法(仿函数)
            std::cout << name <<" run, "<<"result: "<< t.GetResult()<< std::endl;}}voidStart()//启动线程池{int num = threads_.size();for(int i =0; i < num; i++){//创建线程并且给线程池命名
            threads_[i].name ="thread-"+ std::to_string(i +1);pthread_create(&(threads_[i].tid),nullptr, HandlerTask,this);}}
    T Pop(){
        T t = tasks_.front();
        tasks_.pop();return t;}voidPush(const T &t){Lock();
        tasks_.push(t);Wakeup();//队列中有了数据//可能在之前任务为空的时候有线程进入了休眠//所以放入任务后,进行一次唤醒操作Unlock();}static ThreadPool<T>*GetInstance(){if(nullptr== tp_)// 双重判定空指针, 降低锁冲突的概率, 提高性能.{pthread_mutex_lock(&lock_);//这个lock_锁为静态成员if(nullptr== tp_){
                std::cout <<"log: singleton create done first!"<< std::endl;
                tp_ =newThreadPool<T>();}pthread_mutex_unlock(&lock_);}return tp_;}private://因为为懒汉单例模式,只有一个ThreadPool类,所以为了避免//冲突,把构造函数,拷贝构造函数都设置成私有禁用ThreadPool(int num = defalutnum):threads_(num){pthread_mutex_init(&mutex_,nullptr);pthread_cond_init(&cond_,nullptr);}~ThreadPool(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&cond_);}ThreadPool(const ThreadPool<T>&)=delete;const ThreadPool<T>&operator=(const ThreadPool<T>&)=delete;// a=b=cprivate:
    std::vector<ThreadInfo> threads_;
    std::queue<T> tasks_;

    pthread_mutex_t mutex_;
    pthread_cond_t cond_;static ThreadPool<T>*tp_;//懒汉单例模式所创建的//唯一的ThreadPool类static pthread_mutex_t lock_;//静态方法只能访问类中的静态成员,所以还需要一个静态锁在//获取这个对象的时候使用(GetInstance)};//静态成员在类外进行初始化template<classT>
ThreadPool<T>*ThreadPool<T>::tp_ =nullptr;template<classT>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;

3.Main.cpp

#include<iostream>#include<ctime>#include"ThreadPool.hpp"#include"Task.hpp"

pthread_spinlock_t slock;intmain(){
     
     
    std::cout <<"process runn..."<< std::endl;sleep(3);//GetInstance()获取单例对象ThreadPool<Task>::GetInstance()->Start();srand(time(nullptr)^getpid());while(true){//1. 构建任务int x =rand()%10+1;usleep(10);int y =rand()%5;char op = opers[rand()%opers.size()];

        Task t(x, y, op);ThreadPool<Task>::GetInstance()->Push(t);//2. 交给线程池处理
        std::cout <<"main thread make task: "<< t.GetTask()<< std::endl;sleep(1);}}

在这里插入图片描述


本文转载自: https://blog.csdn.net/m0_74774759/article/details/135962347
版权归原作者 Kaugo 所有, 如有侵权,请联系我们删除。

“【Linux】线程池的简易实现(懒汉模式)”的评论:

还没有评论