线程池:一个或多个线程+任务队列;
为什么要有线程池:一个程序起来以后立即启动了很多线程然后取处理任务,如果每来
一个请求都创建一个线程线程占资源,如果一瞬间来了很多请求会把资源耗尽程序奔溃了
应用场景:(1)启动线程处理任务请求,若同一时间因为大量请求创建大量线程有可能导致资源耗尽程序奔溃(需要限制上限)(2)创建线程(t1)+t2(处理任务时间)+t3销毁线程=总花费时间t
t1+t3/t线程创建销毁占的时间比例,如果占的时间比例特别高,大量时间用于创建线程销毁cpu利用率低,解决方法:一开始就创建一堆线程一直不退出,让他们去处理请求
功能:(1)避免峰值压力导致资源耗尽(2)避免大量线程的创建/销毁成本
任务队列:缓冲区,任务交给线程,
如何实现:线程创建和线程安全的任务队列实现+向队列添加任务
线程会从任务队列拿取数据,所以这个任务队列要保证线程安全
C++中stl容器:std::queue实现
成员:std:queue _list任务队列
_cap数量上限
mutex保证安全
Fcond队列为满
Empty队列为空
大量的线程线程的个数intmax_thr最大线程数量
自适应线程:一开始不创建线程,有任务来了查看现在有么有空闲的线程,如果没有创建线程,但是有最大数量上限,如果这些线程在一定的时间段内一直得不到任务(特别闲)为了省资源会让这些线程满满逐一退出,暂时省下资源了
直接创建固定线程:
成员函数里面:queuepush queuepop addtask(添加任务)
创建线程池的时候这些线程的入口函数一样(内部逻辑一样意味着他们只能处理相同的任务代表 一类线程池只能处理一类任务如果任务种类比较多久难以实现了),
Eg:线程处理文本图片和语言,处理方式稍微有不同,入口函数不同,从一个任务队列拿数据可能出错如何解决?
为了通用性着想处理不同请求:不应该直接放在线程的入口函数里面去处理也就是任务的处理方法由任务的本身决定
所以还需要一个任务task
Voiddata 数据
Function如何处理
定义一个结构体把结构体定义成task,task里面有个data和成员函数指针这时候这个函数可以单独实现每次赋值不同函数就可以了–>类–>自己定义成员函数如果把函数设置成固定函数,只能代表这个函数处理一类数据,只有一类处理方法,类里面有一个函数叫虚函数在定义其他的类来继承我这个类,里面那个虚函数可以自己实现每次对不同数据实现不同方法就可以了
Task{
Voiddata
Fuction}
srand(time(NULL));
22 int s=rand()%5;//rand取余5保证随机数在0-4之间
W> 23 printf(“thread %p get data %d–sleep—%d\n”,pthread_self(),
24 _data,s);//区分线程第二个获得的数据第三个睡了几秒钟后
25 //才能再次处理任务
26 sleep(s);
27 }
28 };
29
30 class ThreadPool{
31 private:
32 std::queue<MyTask > _list;
33 int _cap;
34 pthread_mutex_t _mutex;//锁
35 pthread_cond_t _full;//条件变量
36 pthread_cond_t _empty;
37 int _max_thr;//最大成员变量线程池的最大线程数量
38 bool QueuePopIsEmpty(){
39
40 return( _list.size()0);
}
42 bool QueueIsFull(){
W> 43 return (_list.size()_cap);
44 }
45 bool QueuePush(MyTaskt){
46 _list.push(t);
W> 47 }
48 bool QueuePop(MyTask**t){
49 t=_list.front();//获取队首结点
50 _list.pop();//出队
W> 51 }
52 static void thr_start(voidarg){
53 //在类里面定义thr_start会发现入口函数不匹配为什么?类的成员函数有多个参数,默认
54 //会有一个参数这个参数是this指针,现在入口函数有arg一个参数但是它做为类的成员
55 //函数默认前面有个参数是this现在变成两个参数了所以和底下那个start参数不匹配
56 //将其变成静态函数,或者将start定义到类的外面这两者都会导致没有this指针了意味着
57 //函数没办法操作类的成员类的变量了,所以需要将this指针通过thr_start函数传进来
58 //才能操作类的对象,所以在创建线程时候需要传一个参数(void*)this,谁实例化这个
59 //对象传的就是谁
60 while(1){//循环获取任务
ThreadPool p=(ThreadPool)arg;//强转
62 pthread_mutex_lock(&p->_mutex);//私有成员函数可以访问到私有成员
63 MyTasktask;
64 while(p->QueuePopIsEmpty()){//队列为为空则等待(没有任务,工作线程则等待)
65 //此处为while循环不能是if如果是if会唤醒多个,取到相同的就出问题
66 pthread_cond_wait(&p->_empty,&p->_mutex);
67 }
68 p->QueuePop(&task);//把任务获取出来
69 //task->Run();//运行task里面方法run函数有可能处理很长时间导致锁一直加着,其他的
70 //线程获取不到任务导致同一时间只有一个线程来处理任务–>taskrun应该放在锁
71 //的外边
72 pthread_mutex_unlock(&p->_mutex);
73 pthread_cond_signal(&p->_full);//唤醒
74 task->Run();
75 delete task;
76 return NULL;
77 }
78 }
79 public:
80 ThreadPool(int thr=5,int q=10)?/构造函数
//thr最大线程数量,q缓冲队列结点数量
W> 82 _max_thr(thr),_cap(q)
83 {
84 pthread_mutex_init(&_mutex,NULL);
85 pthread_cond_init(&_full,NULL);
86 pthread_cond_init(&_empty,NULL);
87 }
88 ~ThreadPool(){
89 //析构函数
90 pthread_mutex_destroy(&_mutex);
91 pthread_cond_destroy(&_full);
92 pthread_cond_destroy(&_empty);
93 }
94 bool Init(){
95 pthread_t tid;
96 for(int i=0;i<_max_thr;i++){
97 int ret=pthread_create(&tid,NULL,thr_start,(void)this);
98
99 if(ret!=0){
100 printf(“pthread create errno\n”);
return false;
102 }
103 pthread_detach(tid);
104 }
105 return true;
106 }
107 bool AddTask(MyTasktask){//添加任务队列满了也等待,添加任务就是向队列添加数据
108 pthread_mutex_lock(&_mutex);
109 if(QueueIsFull()){
110 pthread_cond_wait(&_full,&_mutex);
111 }
112 QueuePush(task);
113 pthread_mutex_unlock(&_mutex);
114 pthread_cond_signal(&_empty);//满了唤醒一下empty
115 return true;
116 }
117 };
118 int main(){
119 ThreadPool p;
120 MyTaskt;
p.Init();
122 int i=0;
123 while(1){
124 printf(“add task data :%d\n”,i);
125 t=new MyTask(i++);
126 p.AddTask(t);
127
128 }
129 return 0;
130 }
线程安全的单例模式:
单例模式:是一种典型设计模式中的一种,设计模式:大佬提供如何实现一个功能的这种实现方法只需要大佬定义的模板取设计就好了
单例模式是设计模式中的一种指的是一个对象只能被初始化一次
游戏:
Template
Class obj{
T data;//T类型的数据
Obj();//成员函数obj里面要实例化这个对象意味着obj一旦实例化对象意味着这个data也实例化完毕,写了一个游戏
Start();一旦实例化完毕游戏初始化就好了调用start就开始运行了,一个游戏加载很多数据,和很多初始化,加载一次就够了,这个对象只能被初始化一次(实例化一次)这就叫单例模式
如何实现单例模式:最简单的就是static,就是–> static T obj,就只初始化一次意味着设置成静态程序运行的时候就要初始化完毕会导致一种情况(别人用你的app初始化了三分钟游戏才开始)游戏体验差等的时间太多了;如何解决?有些用的时候再初始化,
单例模式:饿汉模式懒汉模式–>初始化放到上面时候
Eg:饿汉模式:吃饭吃完饭理解洗碗下次直接吃,
懒汉模式:吃完饭不洗碗下次吃的时候再洗,洗完后再吃(不用的时候就不初始化,用的时候再初始化)
饿汉模式对用户体验度高:初始化都一次性完成,一旦初始化完毕后序运行十分流畅
懒汉模式:用户体验感觉好了运行初始化快,真正对性能要求比较高每次用的时候初始化就比较慢了
如果每个对象只初始化一次起始意味着后序懒汉模式也初始化一次,后序也不费时间了,只不过将时间分摊到每一个模块了在实际的使用当中各自有各自有点—>懒汉模式用的多,一旦用了懒汉模式,考虑一个问题–>多个线程取做相当于class obj{ static T *data;obj(){data=newT()}start()},多个线程初始化实例化这个类就会new了多次,没有delect,处于非线程安全状态,所以应该class obj{ static T *data;obj(){mutex.lock(); if(data==null) {data=newT() } 不等于就不New了;mutex.unlock() }start()}—>这就是线程安全的单例模式,有饿汉模式和懒汉模式
饿汉模式:直接启动阶段一次性初始化完毕;用户体验不太好,因此程序初始化时间可能较长 ,但是后续运行流畅
懒汉模式:将初始化分摊到各个阶段,使用的时候才初始化,启动阶段用户体验比较好但是第一次运行到某个模块的时候,流畅度不太好;
STL中的容器是否是线程安全的?—>非线程安全的一旦线程安全性能降低,对于不同的容器加锁方式不同性能可能也不同—>
智能指针:对于unique_ptr由于只是在当前diamante块范围内生效,不会涉及线程安全问题
对于shared_ptr多个对象需要共同用一个一个应用技术变量,所以会存在线程安全问题,但是标准库实现的时候考虑到了这个问题基于原子操作(cas)的方式保证shared_ptr能够高效原子的操作引用计数.
linux线程池的实现-线程的单例模式(懒汉饿汉模式)
最新推荐文章于 2024-07-18 22:40:26 发布