IO模式:
阻塞
非阻塞忙轮询
响应式-- 多路IO转接
多路IO转接:
select、poll、epoll
//错了?
//多进程/多线程比多路IO转接慢在哪?
// 多进程或多线程的话,内核需要在多个进程或线程之间切换,消耗比较大
//======================================================================================
在一个应用程序中,会多次使用线程,因此免不了就需要多次线程的创建及销毁。
但是多次线程创建和销毁会带来较大的内存资源消耗,为了 处理这种情况,就是就提出了线程池的概念。
线程池我觉得它是一个虚拟的概念,并不是说真的有一个池子。
它是指我们在需要线程执行操作之前提前创建好一些线程,也就是线程池。
这样做的好处是:
1.降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2.提高响应速度。 当任务到达时,任务可以不需要等到线程创建就能立即执行。
3.提高线程的可管理性。 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,
还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
应用场景:
1.CPU密集型
2.任务比较多、比较小 并发执行大量短期的小任务
浏览器
我实现的这个简易线程池的话,里面有两个比较重要的结构体,一个是关于线程池本身的,一个是关于任务的
关于任务的这个结构体比较简单,就包含一个函数指针,表示要完成的任务,还有就是这个函数的参数。
在线程池本身这个结构体中,里面包含了线程池的相关信息,
有最小、最大线程数,线程tid数组、当前存活的线程数、当前正忙的线程数、…、还有一个比较重要的是 管理线程。
线程池实现的机制:
在创建线程池的时候,我们根据最大线程数进行创建。将创建的线程都阻塞在任务队列的条件变量上
当有任务到来,我们就从阻塞在条件变量上的空闲线程中找一个去执行
同样,我们也会设置一个任务队列的最大数目,如果任务数目没有超过最大,那么新来的任务就会添加到任务队列中,
如果超过的话,就需要种颜色,等到队列中的任务被执行,留出来位置,再给新来的任务
线程池过大,会造成内存资源浪费;线程池过小,会降低程序的并发度。因此我们需要一个线程来管理线程池中的线程,
这个线程暂且叫他管理线程。
管理线程的任务是根据当前存活的线程数、当前正在忙的线程数、任务队列中实际任务数来对线程池进行添加活销毁部分线程
//======================================================================================
线程池
如果每次来一个请求,就调用pthread_create()新建一个thread,等请求结束再调用pthread_exit()释放掉,这样会
浪费资源,于是就想着在server和client连接之前提前创建好一堆线程,也就是线程池,这是一个虚拟的概念。
当客户端发来数据的时候,就让线程池中的某个线程去处理。所以其实server端和线程池中的线程有一个共享区域。
线程池中的线程在没有任务可以执行的时候,是阻塞在条件变量上的,pthread_cond_wait(),当有任务来的时候,
用pthread_cond_signal() [激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个]或
pthread_cond_broadcast()[激活所有等待线程] 唤醒线程去处理,处理完毕后,该线程重新回到线程池中阻塞等待任务到来。
打开一个火狐浏览器 用ps -aux | grep fairfox查找,发现只有一个,加入进程号为123456
然后再 ps -Lf 123456 发现有一堆LWP(理解为线程),有一个NLWP 它显示的就是LWP的个数,这就是它使用的线程池中线程的个数
多路IO转接主要负责的是客户端和服务器端的连接,而线程池负责的是服务端收到客户端的数据之后的处理
两者是要结合
typedef struct {
void *(*funtion)(void*); //函数指针,回调函数
void *arg; //回调函数的参数
}threadpool_task_t; //各子线程任务结构体
//线程池相关信息
struct threadpool_t{
pthread_mutex_t lock; //锁住本结构体
pthread_mutex_t thread_counter; //记录处于忙状态线程个数的锁
pthread_cond_t queue_not_full; //当任务队列满时,添加任务的线程阻塞,等待此条件变量
pthread_cond_t queue_not_empty; //当任务队列不为空时,通知等待任务的线程
pthread_t *threads; //线程tid数组
pthread_t adjust_tid; //管理线程tid
threadpool_task_t *task_queue; //任务队列(数组首地址)
int min_thr_num; //线程池最小线程数目
int max_thr_num; //线程池最大线程数目
int live_thr_num; //当前存活线程个数
int busy_thr_num; //当前忙状态线程个数
int wait_exit_thr_num; //要销毁的线程个数
//借助环形队列打造的任务队列,因此需要队头指针和队尾指针
int queue_front; //队头指针
int queue_rear; //队尾指针 ---> int ???
int queue_size; // 任务队列中实际任务数
int queue_max_size; //任务队列中可容纳任务上限
int shutdown; //标志位,线程池使用状态,true或者false
}
线程池模块分析:
1. main()
创建线程池
向线程池中添加任务。借助回调函数处理任务
销毁线程池
2. pthread_create();
创建线程池结构体 指针
初始化线程池结构体 变量
创建N个任务线程
创建一个管理者线程
失败时销毁开辟的所有空间
3. 子线程的回调函数 threadpool_thread()
接收参数 void *arg --> pool 结构体
加锁 --> lock : 整个结构体的锁
判断条件变量 --> wait
4. 管理者线程 adjust_thread()
接收参数 void *arg --> pool 结构体
循环10s执行一次
·上锁获得管理者需要的变量 queue_size live_thr_num busy_thr_num
·根据既定算法,使用上述3个变量,判断是否应该创建、销毁线程池中指定步长的线程
5. threadpool_add()
总功能:
模拟产生任务 num[20]
设置回调函数,处理任务 sleep(1)代表处理完成
内部实现:
加锁
初始化任务队列结构体成员:回调函数 function 和 参数 arg
利用环形队列机制,实现添加任务。 借助队尾指针挪移 % 实现
唤醒阻塞在 条件变量上的线程
解锁
6. 从3.中的wait之后继续执行,处理任务。
加锁
获取处理任务的回调函数及其参数
利用环形队列机制,实现处理任务。 借助队头指针挪移 % 实现
唤醒阻塞在条件变量上的 server
解锁
加锁
修改忙线程数目 +1
解锁
执行回调函数
加锁
修改忙线程数目 -1
解锁
7. 创建、销毁线程
管理者线程根据 task_num, live_num, busy_num
根据既定算法,使用上述3变量,判断是否应该 创建、销毁线程池中 指定步长的线程
如果满足 创建条件
pthread_creat 回调 任务线程? live_thr_num++
如果满足 销毁条件
wait_exit_thr_num = 10;
使用signal 给阻塞在条件变量上的线程发送 假条件满足信号, 跳转至pthread_cond_wait
阻塞的线程会被假信号唤醒 pthread_exit() 销毁
void *threadpool_thread(void* threadpool);
void *adjust_thread(void* threadpool);
void *threadpool_add(threadpoll_t *pool, void *(*function)(void *arg), void *arg);
void *threadpool_destory(threadpoll_t *pool);
void *threadpool_free(threadpoll_t *pool);
void *process(void* arg);
int main(void)
{
threadpoll_t *thp = threadpoll_create(3,100,100);
printf("poll inited");
//模拟任务产生
int num[20],i;
for(i=0;i<20;i++)
{
num[i] = i;
printf("add task %d\n",i);
threadpool_add(thp, //线程池结构体
process, //回调函数,表示要处理的任务
(void*)&num[i]);
}
sleep(10); //等待子线程完成任务
threadpool_destory(thp);
}
threadpoll_t *threadpoll_create(int min_thr_num, int max_thr_num, int queue_max_size)
{
int i=0;
threadpool_t *pool = NULL;
do{
if((pool= (threadpoll_t *)malloc(sizeof(threadpoll_t)))==NULL)
{
printf("malloc threadpool fail!");
break;
}
pool->min_thr_num = min_thr_num;
pool->max_thr_num = max_thr_num;
pool->busy_thr_num = 0;
pool->live_thr_num = min_thr_num;
pool->wait_exit_thr_num = 0;
pool->queue_size = 0;
pool->queue_max_size = queue_max_size;
pool->queue_front = 0;
pool->queue_rear = 0;
pool->shutdown = false; //不关闭线程池
//根据最大线程上限,给工作线程数组开辟空间并清零
pool->threads = (pthread_t*)malloc(sizeof(pthread_t)*max_thr_num);
if(pool->threads==NULL)
{
printf("malloc threads fail!");
break;
}
memset(pool->threads,0,sizeof((pthread_t)*max_thr_num));
//给 任务队列 开辟空间 不用清零??
pool->task_queue = (pthread_t*)malloc(sizeof(threadpool_task_t)*queue_max_size);
if(pool->task_queue==NULL)
{
printf("malloc task_queue fail!");
break;
}
//初始化互斥锁和条件变量
if(pthread_mutex_init(&(pool->lock),NULL)!=0
|| pthread_mutex_init(&(pool->thread_counter),NULL) !=0
|| pthread_cond_init(&(pool->queue_not_empty),NULL) !=0
|| pthread_cond_init(&(pool->queue_not_full),NULL) !=0)
{
printf("init the lock or cond fail!");
break;
}
//启动min_thr_num个 work thread
for(i=0;i<min_thr_num;i++){
/*
#include <pthread.h>
int pthread_create(
pthread_t *restrict tidp, //新创建的线程ID指向的内存单元。
const pthread_attr_t *restrict attr, //线程属性,默认为NULL
void *(*start_rtn)(void *), //新创建的线程从start_rtn函数的地址开始运行
void *restrict arg //默认为NULL。若上述函数需要参数,将参数放入结构中并将地址作为arg传入。
);
*/
pthread_create(&(pool->thread[i]), NULL, threadpool_thread,(void*)pool);//会把整个线程池的属性传给每个子线程
printf("start thread 0x%x...\n",(unsigned int)pool->threads[i]);
}
//创建管理者线程
pthread_create(&(pool->adjust_tid),NULL,adjust_thread,(void*)pool);
return pool;
}while(0); // do...while(0) 在这里是代替goto语句
threadpoll_free(pool); //前面代码出错,释放pool申请的空间
return NULL;
}
//线程池中各个工作线程 不理解??
void *threadpool_thread(void* threadpool)
{
threadpool_t *pool = (threadpool_t*) threadpool; //强制类型转换
threadpool_task_t task;
while(true){
pthread_mutex_lock(&(pool->lock));
while((pool->queue_size == 0)&& (!pool->shutdown)){ //满足线程池开启 但是 任务队列为空
printf("thread 0x%x is waiting\n", (unsigned int)pthread_self());
//无条件等待,要配合互斥锁一起使用
//pthread_cond_wait作用:阻塞、mutex解锁、...(等待信号)、mutex加锁 https://blog.csdn.net/zzran/article/details/8830213
pthread_cond_wait(&(pool->queue_not_empty), &(pool->lock));
if(pool->wait_exit_thr_num > 0){
pool->wait_exit_thr_num--;
if(pool->live_thr_num > pool->min_thr_num){
printf("thread 0x%x is exiting\n", (unsigned int)pthread_self());
pool->live_thr_num--;
pthread_mutex_unlock(&(pool->lock));
pthread_exit(NULL);
}
}
}
if(pool->shutdown)
{
pthread_mutex_unlock(&(pool->lock));
printf("thread 0x%x is exiting\n",(unsigned int)pthread_self());
pthread_detach(pthread_selr\f());
pthread_exit(NULL);
}
//从任务队列里获取任务,是一个出队操作
task.function = pool->task_queue[pool->queue_rear].function;
task.arg = pool->task_queue[pool->queue_rear].arg;
pool->queue_front = (pool->front + 1)% pool->queue_max_size; //队尾指针移动,模拟环形队列
pool->queue_size--;
pthread_cond_broadcast(&(queue_not_full));
pthread_mutex_unlock();
printf("thread 0x%x is working\n",(unsigned int)pthread_self());
pthread_mutex_lock(&(pool->thread_counter));
pool->busy_thr_num++;
pthread_mutex_unlock(&(pool->thread_counter));
(*(task.function))(task.arg); //执行任务回调函数
printf("thread 0x%x end working\n",(unsigned int)pthread_self());
pthread_mutex_lock(&(pool->thread_counter));
pool->busy_thr_num--;
pthread_mutex_unlock(&(pool->thread_counter));
}
pthread_exit(NULL);
}
//管理者线程
#define DEFAULT_TIME 10
void *adjust_thread(void* threadpool)
{
threadpool_t *pool = (threadpool_t*) threadpool;
while(!shutdown)
{
sleep(DEFAULT_TIME);
//取出来管理者线程需要的数据
pthread_mutex_lock(&(pool->lock));
int queue_size = pool->queue_size; // 关注 任务数
int live_thr_num = pool->live_thr_num; // 存活线程数
pthread_mutex_unlock(&(pool->lock));
pthread_mutex_lock(&(pool->thread_counter));
int busy_thr_num = pool->busy_thr_num; //忙线程数
pthread_mutex_unlock(&(pool->thread_counter));
if(queue_size >= MIN_WAIT_TASK_NUM && live_thr_num < pool->max_thr_num){
pthread_mutex_lock(&(pool->lock))
int add = 0;
for(int i=0;i<pool->max_thr_num && add<DEFAULT_THREAD_VARY
&& pool->live_thr_num<pool->max_thr_num;i++){
if(pool->threads[i] ==0 || !is_thread_alive(pool->threads[i])){
pthread_creat
}
}
}
}
}
void *threadpool_add(threadpoll_t *pool, void *(*function)(void *arg), void *arg)
{
pthread_mutex_lock(pool->lock);
while(pool->queue_size == pool->queue_max_size && (!pool->shutdown))
{
pthread_cond_wait(&(pool->queue_not_full),&(pool->lock));
}
if(pool->shutdown)
{
// 如果发现线程池关闭,则唤醒所有正因任务队列为空的线程
pthread_cond_broadcast(&(pool->queue_not_empty));
pthread_mutex_unlock(&(pool->lock));
return 0;
}
if(pool->task_queue[pool->queue_rear].arg != NULL)
pool->task_queue[pool->queue_rear].arg = NULL;
pool->task_queue[pool->queue_rear].function = function;
pool->task_queue[pool->queue_rear].arg = arg;
pool->queue_rear = (pool->rear + 1)% pool->queue_max_size; //队尾指针移动,模拟环形队列
pool->queue_size++; //向任务队列中添加一个任务,因此实际任务个数+1
pthread_cond_signal(&(pool->queue_not_empty));
pthread_mutex_unlock(&(pool->lock));
return 0;
}
void *threadpool_destory(threadpoll_t *pool)
{
if(pool == NULL)
return -1;
pool->shutdown = true;
//先销毁管理者线程
pthread_join(pool->adjust_tid,NULL); //阻塞主线程等待子线程退出
for(int i=0;i<pool->live_thr_num;i++){
pthread_cond_broadcast(&(pool->queue_not_empty)); //通知所有的空闲线程
}
for(int i=0;i<pool->live_thr_num;i++){
pthread_join(pool->threads[i],NULL);
}
threadpoll_free(pool);
return 0;
}
/* 线程池中的线程,模拟业务处理 */
void *process(void *arg)
{
printf("thread 0x%x working on task %d\n", (unsigned int)pthread_self(),(int)arg);
sleep(1); //要处理的任务,这里写sleep模拟小写转大写的具体操作 所以这里的arg参数应该为客户端的文件描述符
printf("task %d id end\n", (int)arg);
return NULL;
}
void *threadpool_free(threadpoll_t *pool)
{
if(pool==NULL)
return -1;
if(pool->task_queue)
free(pool->task_queue);
if(pool->threads){
free(pool->threads);
pthread_mutex_lock(&(pool->lock));
pthread_mutex_destory(&(pool->lock));
pthread_mutex_lock(&(pool->thread_counter));
pthread_mutex_destory(&(pool->thread_counter));
pthread_cond_destory(&(pool->queue_not_full));
pthread_cond_destory(&(pool->queue_not_empty));
}
free(pool);
pool=NULL;
return 0;
}