linux多线程(3)----线程池 +单例模式

线程池

线程池是一种线程使用模式,是典型的一种对生产者与消费者模型的应用。

实现思想:一个或多个创建好的线程+线程安全的任务队列
其他线程将需要处理的任务添加到线程池的任务队列中,线程池中的线程不断的从对列中获取任务进行处理。线程池中已创建好多个线程,这些线程可以复用,执行完一个任务不会被销毁。

优点:

  1. 线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个创建好的线程,等待着监督管理者分配可并发执行的任务。这避免了频繁大量创建和销毁线程的成本。
  2. 线程池不仅能够保证内核的充分利用,还能防止过分调度。避免了峰值压力的情况下,资源不加限制的使用导致资源耗尽的风险。

线程池的应用场景:

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

整体设计思路是:先创建线程和任务队列,然后任务队列不断的任务入队,线程调用Run()进入线程入口函数循环获取任务处理。
任务类实现思想:

/任务类实现思想
//定义一个函数类型指针*handler_t 返回值void 参数 int; 
typedef void(*handler_t)(int);
//任务处理函数,需要任务入队时把处理方式传入

class ThreadTask{  //任务类,提供任务数据和处理任务函数
  public:
    ThreadTask()
    {}
    ThreadTask(int data,handler_t handler):_handler(handler),_data(data) 
    {}
    void Run(){
      _handler(_data);//线程池的线程获取到任务节点调用run就可以完成任务处理
    }
  private:
    int _data;//要处理的数据
    handler_t _handler; //数据的处理方法
};

线程池类设计:

///线程池类
class ThreadPool{                                       
  public:                                               
    ThreadPool(int maxq=MAX_QUEUE,int maxt=MAX_THREAD)
      :max_thread(maxt),_queue(maxq)
    {
		//创建线程
    }                                                
    bool TaskPush(const ThreadTask & task){
		//任务入队
    }
  private:
    static void* thr_entry(void*arg){ //线程入口函数
    }
  private:
    int  max_thread;   //线程池中最大线程数量                                   
    BlockQueue _queue;   //线程安全的队列存放任务数据。 
};    

线程安全的队列 :BlockQueue _queue—>采用之前写的生产者与消费者模型(互斥锁和条件变量实现)中的队列

完整代码:

#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <pthread.h>
#include <queue>
#include <cstdio>

#define MAX_THREAD  5    //线程数
#define  MAX_QUEUE 10   //任务队列容量

//定义一个函数类型指针*handler_t 返回值void 参数 int; 
typedef void(*handler_t)(int);
//任务处理函数,需要任务入队时把处理方式传入

class ThreadTask{  //任务类,提供任务数据和处理任务函数
  public:
    ThreadTask()
    {}
    ThreadTask(int data,handler_t handler):_handler(handler),_data(data) 
    {}

    void Run(){
      _handler(_data);//线程池的线程获取到任务节点调用run就可以完成任务处理
    }
  private:
    int _data;//要处理的数据
    handler_t _handler; //数据的处理方法
};


class BlockQueue{   //线程安全的队列
  public:
    BlockQueue(int que_capacity=MAX_QUEUE):_capacity(que_capacity)
  {  
    pthread_mutex_init(&_mutex,NULL);
    pthread_cond_init(&_cond_p,NULL);
    pthread_cond_init(&_cond_c,NULL);
  }

    ~BlockQueue(){
      pthread_mutex_destroy(&_mutex);
      pthread_cond_destroy(&_cond_p);
      pthread_cond_destroy(&_cond_c);
    }

    bool push(const ThreadTask& data){
      pthread_mutex_lock(&_mutex);
      if(_queue.size()==_capacity){
        pthread_cond_wait(&_cond_p,&_mutex);//队列已满则等待条件满足再放数据到队列
      }
      _queue.push(data);
      pthread_cond_signal(&_cond_c); // 唤醒
      pthread_mutex_unlock(&_mutex);
      return true;
    }

    bool pop(ThreadTask *data){
      pthread_mutex_lock(&_mutex);
      if(_queue.empty()){
        pthread_cond_wait(&_cond_c,&_mutex);//无数据则等待
      }
      *data = _queue.front();
      _queue.pop();
      pthread_cond_signal(&_cond_p);
      pthread_mutex_unlock(&_mutex);
      return true;
    }

  private:
    int _capacity;
    std::queue<ThreadTask> _queue;  
    pthread_cond_t _cond_p; //生产者条件变量
    pthread_cond_t _cond_c;  //消费者条件变量
    pthread_mutex_t _mutex;  //互斥锁
};

class ThreadPool{                                       
  public:                                               
    ThreadPool(int maxq=MAX_QUEUE,int maxt=MAX_THREAD)
      :max_thread(maxt),_queue(maxq)
    {
      int ret;
      pthread_t tid;
      for(int  i =0;i<maxq;++i){
        ret = pthread_create(&tid,NULL,thr_entry,this);
        //创建 把当前线程传入到线程入口函数,thr_entry中当前进程取任务处理
        if(ret!=0){
          exit(-1);
        }
        pthread_detach(tid);//等待
      }
    }                                                
    bool TaskPush(const ThreadTask & task){
      _queue.push(task);
      return true;
    }
  private:
    static void* thr_entry(void*arg){
      ThreadPool* p = (ThreadPool*)arg;
      while(1){
          ThreadTask task;
          p->_queue.pop(&task);//取出任务
          task.Run(); //调动任务的处理函数
      }
      return NULL;
    }
  private:
    int  max_thread;                                      
    BlockQueue _queue;    
};    


void testfunc(int data){ //自定义的任务处理函数,这里简单打印查看结果
 printf("%p--get data:%d, sleep %d sec\n", pthread_self(),
                 data, (data % 3) + 1);
     sleep((data % 3) + 1);
}
int  main ()
{  
  ThreadPool pool;
  for(int i=0;i<20;i++){
    ThreadTask task(i,testfunc);//创建个任务
    pool.TaskPush(task);
  }
  while(1){
    sleep(1);
  }
  return 0;
}

结果:打印放入的数据,和休眠时间,传入不同的数据休眠时间不同
在这里插入图片描述
可以看出先休眠1s的线程处理完数据休眠时间较短,会先再次被调度处理数据。

在这里插入图片描述

四种不同线程池的种类及应用场景:

转载:线程池的种类以及使用场景

CachedThreadPool

这类线程池的特点就是里面没有核心线程,全是非核心线程,其maximumPoolSize设置为Integer.MAX_VALUE,线程可以无限创建,当线程池中的线程都处于活动状态的时候,线程池会创建新的线程来处理新任务,否则会用空闲的线程来处理新任务,这类线程池的空闲线程都是有超时机制的,keepAliveTime在这里是有效的,时长为60秒,超过60秒的空闲线程就会被回收,当线程池都处于闲置状态时,线程池中的线程都会因为超时而被回收,所以几乎不会占用什么系统资源。任务队列采用的是SynchronousQueue,这个队列是无法插入任务的,一有任务立即执行,所以CachedThreadPool比较适合任务量大但耗时少的任务。

FixedThreadPool

这类线程池的特点就是里面全是核心线程,没有非核心线程,也没有超时机制,任务大小也是没有限制的,数量固定,即使是空闲状态,线程不会被回收,除非线程池被关闭,从构造方法也可以看出来,只有两个参数,一个是指定的核心线程数,一个是线程工厂,keepAliveTime无效。任务队列采用了无界的阻塞队列LinkedBlockingQueue,执行execute方法的时候,运行的线程没有达到corePoolSize就创建核心线程执行任务,否则就阻塞在任务队列中,有空闲线程的时候去取任务执行。由于该线程池线程数固定,且不被回收,线程与线程池的生命周期同步,所以适用于任务量比较固定但耗时长的任务。

ScheduledThreadPool

这类线程池核心线程数量是固定的,好像和FixThreadPool有点像,但是它的非核心线程是没有限制的,并且非核心线程一闲置就会被回收,keepAliveTime同样无效,因为核心线程是不会回收的,当运行的线程数没有达到corePoolSize的时候,就新建线程去DelayedWorkQueue中取ScheduledFutureTask然后才去执行任务,否则就把任务添加到DelayedWorkQueue,DelayedWorkQueue会将任务排序,按新建一个非核心线程顺序执行,执行完线程就回收,然后循环。任务队列采用的DelayedWorkQueue是个无界的队列,延时执行队列任务。综合来说,这类线程池适用于执行定时任务和具体固定周期的重复任务。

SingleThreadPool
这类线程池顾名思义就是一个只有一个核心线程的线程池,从构造方法来看,它可以单独执行,也可以与周期线程池结合用。其任务队列是LinkedBlockingQueue,这是个无界的阻塞队列,因为线程池里只有一个线程,就确保所有的任务都在同一个线程中顺序执行,这样就不需要处理线程同步的问题。这类线程池适用于多个任务顺序执行的场景。

单例模式

单例模式:一种非常典型的设计模式
思想:一个类只能实例化一个对象(资源在内存中只能有一分)
作用:防止内存中资源冗余,防止数据混淆。
应用场景:程序运行所需配置…
  (1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
  (2)控制资源的情况下,方便资源之间的互相通信。如线程池等。

实现:饿汉方式 / 懒汉方式
饿汉方式:以空间换时间。在程序初始化阶段完成资源的初始化加载,用的时候直接用。用于对程序运行速度较高的
懒汉方式:延迟加载。资源在程序使用的时候再去初始化加载,程序启动时较慢。(用到的时候加载)

代码实现:
饿汉方式:

template<class T>
class Singleton{
public:	
	T* GetInstance(){
		return &_data;
	}

private:
	static 	Singleton eton;//静态 构造函数私有化,一个类只能实例化一个对象,在类中实例化对象.
private:
	static T _data;  //静态资源 共用一份,静态成员在程序初始化时完成资源初始化加载。
};


懒汉方式:资源运行时加载资源需考虑线程安全问题(加锁保护)。

template <class T>
class Singleton{
public:
	volatile T* GetInstance(){  //volatile  资源方问太频繁 防止编译器过度优化,
		if(_data==NULL){  //二次检测 资源已存在则不进入 否则进入加锁解锁影响性能
			_mutex.lock();
			if(_data==NULL){
				_data =new T();
			}
			_mutex.unlock();
		}
		return _data;
	}

private:
	volatile static T *_data;  //程序初始化时只初始化了指针,并未加载指针指向的资源。
	static std::mutex _mutex;
};

STL容器都是非线程安全的;智能指针都是线程安全的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值