Linux多线程(下)——线程应用

线程池

概念
  • 概念:创建一堆线程,循环等待处理任务,是非常典型的消费者与生产者模型;
  • 原理:一堆线程 + 线程安全的任务队列,其他线程将任务抛入线程安全的任务队列中,线程池中的线程从任务队列中获取任务进行处理;
优点
  • 线程池优点:从使用线程池和不适用线程池两方面来说
    • 不使用线程池:若要处理大量请求,使用单执行流程效率较低,因此采用多执行流程(多线程)来提高处理效率——产生一个请求,则创建一个线程,然后去处理请求,最后进行线程销毁;
      若处理一个任务的总时间中,创建线程与销毁线程的时间占据大量时间比例,那么则意味着 CPU 资源大部分时间都消耗在线程的创建与销毁而不是处理任务,这样会影响缓存局部性和整体性能;
    • 使用线程池:线程池中的线程在创建之后不销毁,而是循环从队列中取出任务进行处理,避免了频繁进行线程的创建与销毁带来的时间成本与代价;
      线程池不仅能够保证内核的充分利用,还能防止过分调度,因为线程池中创建的线程以及缓冲区大小有最大数量的限制,所以如果在一瞬间有大量任务涌入,就可以避免峰值压力带来的风险;
应用
  1. 需要大量的线程来完成任务,且完成任务的时间比较短:WEB 服务器完成网页请求这样的任务,使用线程池技术是非常合适的,因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数,但对于长时间的任务,比如一个 Telnet 连接请求,线程池的优点就不明显了,因为 Telnet 会话时间比线程的创建时间大多了;
  2. 对性能要求苛刻的应用:比如要求服务器迅速响应客户请求;
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用:突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,但是短时间内产生大量线程可能使内存到达极限,出现错误;
实现
  • 实现:线程池的作用就是针对大量任务进行处理,但是任务类型多种多样,而线程入口函数却是固定的,如何实现工作线程针对不同的任务进行不同的处理呢?
    1. 在线程入口函数中,分辨任务类型,调用不同处理接口,不过该方法不太好实现,需要程序员来手动确定传入的任务如何处理,如果一百个任务有一百种处理方法,那么对程序员是不友好的;
    2. 其他线程在通过任务队列传入任务的同时,也把这个任务的处理方法传进来,线程池中的线程只需要使用处理方法来处理任务即可,不用关注什么样的任务该怎样去处理,当一个没有感情的处理任务机器,该方法十分推荐;
#include <iostream>                                                                      
#include <queue>
#include <pthread.h>
//定义两个宏来代表阻塞队列中可存放的最大任务数和可创建的最大线程数
#define MAX_QUEUE   10
#define MAX_THREAD  5
//定义一个任务处理函数的函数指针
typedef void (*handler_t)(int data);
//任务类


class ThreadTask{
private:
    int _data;//要处理的数据
    handler_t _handler;//任务处理函数的函数指针
public:
	//默认构造函数
    ThreadTask() {}
    //带参数的构造函数,参数指明了数据、处理任务函数的函数指针
    ThreadTask(int data, handler_t handler)
    	:_data(data)
    	, _handler(handler)
    {}
    //对成员变量进行设置的函数
    void SetTask(int data, handler_t handler) {
        _data = data; //设置数据
        _handler = handler; //设置任务处理函数
    }
    //执行任务的函数
    void Run() {
    	//函数指针的用法和函数一样:函数指针(参数列表) 即可
        return _handler(_data);
    }
};
//存储任务的阻塞队列类,这里是复用了前面:用条件变量实现的消费者与生产者模型的阻塞对队列
class BlockQueue{
private:
    int _capacity;//容量
    std::queue<ThreadTask> _queue;//实例化任务类类型的队列
    pthread_mutex_t _mutex;//创建互斥量
    pthread_cond_t _cond_pro;//消费者条件变量
    pthread_cond_t _cond_cus;//生产者条件变量
public:
	//构造函数
    BlockQueue(int cap = MAX_QUEUE)
    	:_capacity(cap) 
    {
    	//初始化互斥量和条件变量
        pthread_mutex_init(&_mutex, NULL);
        pthread_cond_init(&_cond_pro, NULL);
        pthread_cond_init(&_cond_cus, NULL);
    }       
    //析构函数
    ~BlockQueue() {
    	//销毁互斥量和条件变量
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond_pro);
        pthread_cond_destroy(&_cond_cus);
    }  
    //存入任务类数据
    bool Push(ThreadTask &data) {
        pthread_mutex_lock(&_mutex); //先加锁
         //判断阻塞队列是否存储满了
        while (_queue.size() == _capacity) {
        	//如果满了则阻塞等待任务进行处理
            pthread_cond_wait(&_cond_pro, &_mutex);
        }
        //如果没满,那么则将任务放入阻塞队列中
        _queue.push(data);
        //唤醒处理任务的线程
        pthread_cond_signal(&_cond_cus);
        pthread_mutex_unlock(&_mutex); //解锁
        return true;                                                                 
    }
    //从阻塞队列中拿出任务
    bool Pop(ThreadTask *data) {
    	//加锁
        pthread_mutex_lock(&_mutex);
        //判断队列是否为空
        while (_queue.empty()) {
        	//如果为空那么则阻塞等待生产者创建任务
            pthread_cond_wait(&_cond_cus, &_mutex);
        }
        //如果有任务等待处理,那么则取出任务类结点
        *data = _queue.front();
        _queue.pop();
        //唤醒生产者进行任务的生产
        pthread_cond_signal(&_cond_pro);
        pthread_mutex_unlock(&_mutex); //解锁
        return true;
    }
};
//线程池类
class ThreadPool{
public:
	//构造函数
    ThreadPool(int tnum = MAX_THREAD, int qnum = MAX_QUEUE)
    	:_thread_num(tnum) //设置线程最大数量
    	, _queue(qnum)   //开辟指定空间的队列
    {}
    //线程池初始化函数
    bool Init(){
        int ret;
        pthread_t tid;
        //循环创建最大数量的线程
        for (int i = 0; i < _thread_num; i++) {
************//创建线程执行指定的执行流程,这里需要注意一个小细节:
************//创建线程函数的第三个参数是一个函数指针,而这个函数指针所指向的函数是:无返回值且只有一个 void* 类型的参数
************//而我们所传入的函数指针所指向的函数是该类中的成员函数,所以本身含有一个 this 指针,那么就不满足函数指针的要求,所以我们可以将传入的函数指针所指向的函数定义为静态成员函数,而静态成员函数是不包含 this 指针的,所以满足要求
************//不过这样我们需要额外的传入 this 指针,否则在传入函数的内部无法对一些成员变量进行操作,所以创建线程函数的第四个参数为 this 指针
            ret = pthread_create(&tid, NULL, thr_entry, (void*)this);
            //如果创建失败则返回
            if (ret != 0) {
                std::cout << "thread create error\n";
                return false;
            }
            //直接将创建的线程设置为分离属性,这样就不用等待线程退出了
            pthread_detach(tid);
        }
        return true;
    }
    //向阻塞队列中传入任务结点的函数
    bool Push(ThreadTask &task){
        _queue.Push(task);
        return true;
    }
private:
	//处理任务的函数,也就是线程的执行流程函数
    static void *thr_entry(void *arg) {
    	//这是一个静态函数,所以没有 this 指针,我们通过参数将 this 指针传进来
        ThreadPool* this = (ThreadPool*)arg;
        //循环从阻塞队列中取出任务进行执行
        while(1) {
            ThreadTask task;
            //利用 this 指针来操作对应对象的成员变量
            this->_queue.Pop(&task);
            task.Run();
        }
        return NULL;
    }
private:
	//线程的最大数量
    int _thread_num;
    //创建阻塞队列
    BlockQueue _queue;
};
int main (int argc, char *argv[]){
    return 0;
}                                                                                        

单例模式

概念
  • 单例模式:是一种非常典型的设计模式,该模式下,一份资源只能被加载一次、一个类只能实例化一个对象,并且向外提供统一的接口来供使用;
实现
  • 饿汉方式:资源在程序初始化阶段就完成加载——以空间换时间;
    • 静态修饰资源:保证资源只有一份,并且静态成员在程序初始化阶段就完成了初始化加载;
    • 构造函数私有化:在外部无法进行对象的构造,因此可以保证类的对象只有一个,我们可以在类内部构造一个对象以供使用;
template<class T>
class singleton{
private:
	//资源静态,保证只有一份
	static T _data;
	//构造函数私有,保证对象只有一个
	singleton(){}
public:
	T* get_data(){
		return &_data;
	}
};
  • 懒汉方式:资源在使用的时候再去加载——延迟加载;
    • 静态修饰资源指针:静态可以保证资源指针只被加载一份,指针可以在使用时再加载资源,也就是延迟加载;
    • volatile修饰资源指针:防止编译器过度优化,毕竟资源在使用时才会加载到指针,所以如果编译器过度优化,那么就不能拿到资源了;
    • 构造函数私有化:在外部无法进行对象的构造,因此可以保证类的对象只有一个,我们可以在类内部构造一个对象以供使用;
    • 加锁保护:因为是使用时才进行加载资源,所以在多线程运行下,很可能多个线程对资源进行访问,发现此时没有资源,然后都向其加载资源,那么会造成资源重复加载问题,设置互斥锁就可避免这种情况,保护线程安全;
    • 二次检测:当多个线程去访问资源时,如果资源不存在,我们再进行加锁,然后加载资源,这样子就可以避免当资源存在时也有线程去加锁的问题,可以大大提高效率;
template<class T>
class singleton{
private:
	//资源静态,保证只有一份
	volatile static T _data;
	//构造函数私有,保证对象只有一个
	singleton(){}
	//设置互斥锁保护资源访问
	static std::mutex _mutex;
public:
	//防止编译器过度优化
	volatile T* get_data(){
		//二次检测,如果没有加载资源,那么我们才会加锁,并且进行资源加载,不用每个线程都加锁,从而避免浪费时间
		if(_data == nullptr){
			//加锁保护
			_mutex.lock();
			if(_data == numllptr){
				_data = new T();
			}
			_mutex.unlock();	
		}
		return &_data;
	}
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值