Linux高性能服务器编程:进程池和线程池原理及应用(有图有代码有真相!!!)

一、问题引入

在前面编写多进程、多线程服务器时通过动态创建子进程和子线程来实现并发服务器,这样做有以下缺点:

1)动态创建进程、线程将会比较耗费时间,将导致较慢的客户响应。

2)动态创建的子进程只为一个客户服务,将会产生大量的细微进程或线程,进程或线程之间的切换将耗费CPU大量的时间。

3)动态创建的子进程是当前进程的完整映像,当前进程必须谨慎管理其分配的文件描述符和堆内存等系统资源,否则子进程可能复制这些资源,使系统可用资源急剧下降,

进而影响服务器性能。

要解决这些问题就要引入进程池、线程池。

二、进程池、线程池

1、池的概念

由于服务器的硬件资源“充裕”,那么提高服务器性能的一个很直接的方法就是以空间换时间,即“浪费”服务器的硬件资源,以换取其运行效率。

这就是池的概念。池是一组资源的集合,这组资源在服务器启动之初就完全被创建并初始化,这称为静态资源分配。当服务器进入正是运行阶段

,即开始处理客户请求的时候,如果它需要相关的资源,就可以直接从池中获取,无需动态分配。很显然,直接从池中取得所需资源比动态分配资

源的速度要快得多,因为分配系统资源的系统调用都是很耗时的。当服务器处理完一个客户连接后,可以把相关的资源放回池中,无需执行系统调

用来释放资源。从最终效果来看,池相当于服务器管理系统资源的应用设施,它避免了服务器对内核的频繁访问。

池可以分为多种,常见的有内存池、进程池、线程池和连接池。


2、 进程池、线程池

进程池和线程池相似,所以这里我们以进程池为例进行介绍。如没有特殊声明,下面对进程池的讨论完全是用于线程池。进程池是由服务器预先

创建的一组子进程,这些子进程的数目在3~10个之间(当然这只是典型情况)。线程池中的线程数量应该和CPU数量差不多。进程池中的所有子进程都运行着

相同的代码,并具有相同的属性,比如优先级、PGID等。当有新的任务来到时,主进程将通过某种方式选择进程池中的某一个子进程来为之服务。

相比于动态创建子进程,选择一个已经存在的子进程的代价显得小得多。至于主进程选择哪个子进程来为新任务服务,则有两种方法:

1)主进程使用某种算法来主动选择子进程。最简单、最常用的算法是随机算法和Round Robin(轮流算法),但更优秀、更智能的算法使得任务在各个工作进程中均匀的分配

,从而减轻服务器的整体压力

2)主进程和所有子进程通过一个共享的工作队列来同步,子进程都睡眠在该工作队列上。当有新的任务到来时,主进程将任务添加到工作队列中。这将唤醒正在等待任       务的子进程,不过只有一个子进程将获得新任务的“接管权”,它可以从工作队列中取出任务并执行之,而其他子进程将继续睡眠在工作队列上。


当选择好子进程后,主进程还需要使用某种通知机制来告诉目标子进程有新任务需要处理,并传递必要的数据。最简单的方式是,在父进程和子进程之间预先建立好一

条管道,然后通过管道来实现所有的进程间通信。在父线程和子线程之间传递数据就要简单得多,因为我们可以把这些数据定义为全局,那么它们本身就是被所有线程共享的。

三、进程池、线程池的部分应用


1、应用进程池处理多客户

在使用进程池处理多客户任务时,首先要考虑的一个问题是:监听 socket 和连接 socket 是否都由进程统一管理这两种 socket 。这可以一下介绍的并发模式解决。服务器主要有两种并发编程模式:半同步 半异步模式和领导者 追随者模式。

其次,在设计进程池时还需要考虑:一个客户连接上的所有任务是否始终由一个子进程来处理。如果说客户任务是无状态的,那么我们可以考虑使用不同的子进程来为该客户的不同请求服务。但如果客户是存在上下文关系的,则最好一直用同一个子进程来为之服务,否则实现起来比较麻烦,因为我们不得不在各子进程之间传递上下文数据。 epoll 的 EPOLLONESHOT 事件能够确保一个客户连接在整个生命周期中仅被一个线程处理。




2、线程池主要用于:

1)、需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。

2)、对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。

3)、接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,并出现"OutOfMemory"的错误。

3、c模拟实现线程池

 #include <pthread.h> #include <unistd.h>#include <stdlib.h>#include <errno.h>#include <string.h>#include <stdio.h>/* 要执行的任务链表 */
typedef struct tpool_work {void*               (*routine)(void*);       /* 任务函数 */void                *arg;                    /* 传入任务函数的参数 */struct tpool_work   *next;                    }tpool_work_t;typedef struct tpool {int             shutdown;                    /* 线程池是否销毁 */int             max_thr_num;                /* 最大线程数 */pthread_t       *thr_id;                    /* 线程ID数组 */tpool_work_t    *queue_head;                /* 线程链表 */pthread_mutex_t queue_lock;                    pthread_cond_t  queue_ready;    }tpool_t;//创建线程池
int tpool_create(int max_thr_num);
//销毁线程池 void tpool_destroy();int tpool_add_work(void*(*routine)(void*), void *arg);static tpool_t *tpool = NULL;/* 工作者线程函数, 从任务链表中取出任务并执行 */
static void* thread_routine(void *arg)
{tpool_work_t *work;while(1) {// 如果线程池没有被销毁且没有任务执行,会处于阻塞状态,//pthread_cond_wait是一个原子操作,等待前会解锁,唤醒后会加锁 pthread_mutex_lock(&tpool->queue_lock);while(!tpool->queue_head && !tpool->shutdown) {printf ("thread %lu is waiting\n", pthread_self ());pthread_cond_wait(&tpool->queue_ready, &tpool->queue_lock);}//线程池要销毁了if (tpool->shutdown) {pthread_mutex_unlock(&tpool->queue_lock);printf ("thread %lu will exit\n", pthread_self ());pthread_exit(NULL);}work = tpool->queue_head;tpool->queue_head = tpool->queue_head->next;pthread_mutex_unlock(&tpool->queue_lock);work->routine(work->arg);free(work);}return NULL;   }//  * @brief     创建线程池 
//  * @param     max_thr_num 最大线程数
//  * @return     0: 成功 其他: 失int tpool_create(int max_thr_num){int i;tpool = calloc(1, sizeof(tpool_t));if (!tpool) {printf("%s: calloc failed\n", __FUNCTION__);exit(1);}/* 初始化 */tpool->max_thr_num = max_thr_num;tpool->shutdown = 0;tpool->queue_head = NULL;if (pthread_mutex_init(&tpool->queue_lock, NULL) !=0) {printf("%s: pthread_mutex_init failed, errno:%d, error:%s\n",__FUNCTION__, errno, strerror(errno));exit(1);}if (pthread_cond_init(&tpool->queue_ready, NULL) !=0 ) {printf("%s: pthread_cond_init failed, errno:%d, error:%s\n", __FUNCTION__, errno, strerror(errno));exit(1);}/* 创建工作者线程 */tpool->thr_id = calloc(max_thr_num, sizeof(pthread_t));if (!tpool->thr_id) {printf("%s: calloc failed\n", __FUNCTION__);exit(1);}for (i = 0; i < max_thr_num; ++i) {if (pthread_create(&tpool->thr_id[i], NULL, thread_routine, NULL) != 0){printf("%s:pthread_create failed, errno:%d, error:%s\n", __FUNCTION__, errno, strerror(errno));exit(1);}}    return 0;}/* 销毁线程池 */void tpool_destroy(){int i;tpool_work_t *member;if (tpool->shutdown) {return;}tpool->shutdown = 1;/* 通知所有正在等待的线程 */pthread_mutex_lock(&tpool->queue_lock);pthread_cond_broadcast(&tpool->queue_ready);pthread_mutex_unlock(&tpool->queue_lock);for (i = 0; i < tpool->max_thr_num; ++i) {pthread_join(tpool->thr_id[i], NULL);}free(tpool->thr_id);while(tpool->queue_head) {member = tpool->queue_head;tpool->queue_head = tpool->queue_head->next;free(member);}pthread_mutex_destroy(&tpool->queue_lock);    pthread_cond_destroy(&tpool->queue_ready);free(tpool);    }//* @brief     向线程池中添加任务//* @param    routine 任务函数指针//* @param     arg 任务函数参数//* @return     0: 成功 其他:失败 int tpool_add_work(void*(*routine)(void*), void *arg){tpool_work_t *work, *member;if (!routine){printf("%s:Invalid argument\n", __FUNCTION__);return -1;}work = malloc(sizeof(tpool_work_t));if (!work) {printf("%s:malloc failed\n", __FUNCTION__);return -1;}work->routine = routine;work->arg = arg;work->next = NULL;pthread_mutex_lock(&tpool->queue_lock);    member = tpool->queue_head;if (!member) {tpool->queue_head = work;} else {while(member->next) {member = member->next;}member->next = work;}/* 通知工作者线程,有新任务添加 */pthread_cond_signal(&tpool->queue_ready);pthread_mutex_unlock(&tpool->queue_lock);return 0;    }//测试 void *func(void *arg){printf("thread %d\n", (int)arg);return NULL;}int main(int arg, char **argv){if (tpool_create(5) != 0) {printf("tpool_create failed\n");exit(1);}int i;for (i = 0; i < 12; ++i) {//给线程池分配任务tpool_add_work(func, (void*)i);}sleep(2);
	tpool_destroy();//销毁线程池
     return 0;}

结果:





本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/459366.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux:多进程、多线程服务器的实现解析(有图有代码有真相!!!)

一、问题引入 阻塞型的网络编程接口 几乎所有的程序员第一次接触到的网络编程都是从 listen()、send()、recv()等接口开始的。使用这些接口可以很方便的构建服务器 /客户机的模型。 我们假设希望建立一个简单的服务器程序&#xff0c;实现向单个客户机提供类似于“一问一答”的…

数据结构:神奇的B树实现解析(有图有代码有真相!!!)

一、B树引入 二叉搜索树、平衡二叉树、红黑树都是动态查找树&#xff0c;典型的二叉搜索树结构&#xff0c;查找的时间复杂度和树的高度相关O(log2N)。 1&#xff09;数据杂乱无章-------线性查找--O&#xff08;n&#xff09; 2&#xff09;数据有序-------二分查找 ---O(lo…

Linux:dup/dup2 文件描述符重定向函数(有图有代码有真相!!!)

一、dup/dup2 有时我们希望把标准输入重定向到一个文件&#xff0c;或者把标准输出重定向到一个网络连接。系统调用dup和dup2能够复制文件描述符。dup返回新的文件文件描述符&#xff08;没有用的文件描述符最小的编号&#xff09;。 dup2可以让用户指定返回的文件描述符的值…

Linux:I/O多路转接之select(有图有代码有真相!!!)

一、select引入 一次 I/O 分为两个部分&#xff1a;1&#xff09;等待数据就绪 2&#xff09;进行数据转移 1、select 原理&#xff1a; select的原理就是减少等待数据就绪的比重&#xff0c;巧妙的利用等待队列机制让用户进程适当在没有资源可读/写时睡眠&#xff0c;有资…

Linux: I/O多路转接之poll(有图有代码有真相!!!)

一、poll()函数解析 不同与select使⽤用三个位图来表⽰示三个fdset的⽅方式&#xff0c;poll使⽤用⼀一个 pollfd的指针实现。pollfd结构包含了要监视的event和发⽣生的event&#xff0c; 不再使⽤用select“参数-值”传递的⽅方式。同时&#xff0c;pollfd并没有最⼤大数量限…

kalilinux装到u盘上的弊端_付费下载的歌曲,竟然无法在汽车上播放!原因在这里...

『使用某音乐播放器下载了周杰伦的110首歌曲&#xff0c;其中106首是kgm格式&#xff0c;4首mp3格式&#xff0c;装到U盘后&#xff0c;在其它设备播放只有4首mp3格式的可以播放&#xff0c;其它的均无法播放&#xff0c;请问该如何处理&#xff1f;』网友留言截图这是一位网友…

iconsvg image怎么变为path_昆凌是怎么收服天王周杰伦的?这几招太高明了

周杰伦和昆凌又出来撒狗粮了&#xff01;就在前两天(6月2日)&#xff0c;在参加郎朗的婚礼时&#xff0c;#周杰伦搂昆凌看烟花#的消息悄悄上了热搜。视频中&#xff0c;两人并肩站立&#xff0c;一起欣赏着窗外的美景。周杰伦时不时在昆凌的耳边私语几句&#xff0c;看起来很是…

ewebeditor未授权:功能被禁用请先配置授权_SteamPY新功能——外区账号礼物自动领取...

自从PY平台增加了外区代购后发现许多玩家在购买礼物时常会发生收到礼物后准备点击入库时弹出地区不可用的提示这个问题在Steam外区账号一直频繁发生究其因在于Steam账号登录时的IP问题遇到该问题切勿拒收礼物&#xff01;通过Steam客户端清理登录授权注销退出后再次使用账号对应…

Linux: shell 中命令代换 $() 和 ``(有图有代码有真相!!!)

一、命令代换&#xff08;命令替换&#xff09; 由 或 $() 括起来的也是一条命令&#xff0c;shell先执行该命令&#xff0c;再将结果立刻代换到当前命令行中。 简单例子&#xff1a; DATEdate echo $DATE DATE$(date) echo $DATE 执行结果&#xff1a; 二、优缺点&#x…

精雕道路怎么遍弧形_【养护技术】道路“创可贴”——沥青冷补料 六大优势助力道路养护...

点击上面蓝字关注我们微信号&#xff1a;xzgsgl随着城市精细化管理目标不断提高&#xff0c;市政道路养护修补的要求也越来越高。不但对修补的外观、质量有了更高的标准&#xff0c;对修复时限也提出了一定要求&#xff0c;这就要求我们的养护单位快速、优质地完成道路修补任务…

单耳蓝牙耳机怎么连接_蓝牙耳机怎么挑选?推荐性价比高的蓝牙耳机

随着手机逐渐取消了耳机孔&#xff0c;越来越多的人们开始使用上了蓝牙耳机。在当今这个飞速发展的时代&#xff0c;蓝牙耳机无疑成为了新时代的宠儿。无论是上班族还是当代大学生等年轻化群体&#xff0c;耳机的第一选择都是蓝牙耳机。但是面对市面上如此多的蓝牙耳机&#xf…

Linux: shell命令 eval (有图有代码有真相!!!)

一、eval 命令定义 shell中的eval命令将会首先扫描命令行进行所有的替换&#xff0c;然后再执行命令。该命令使用于那些一次扫描无法实现其功能的变量。 该命令对变量进行两次扫描。这些需要进行两次扫描的变量有时候被称为复杂变量。不过这些变量本身并不复杂。eval 命令也可…

qlabel可以选中吗_惊现凡尔赛式排版!原来微信公众号排版样式还可以“变装”?...

各位小伙伴们&#xff0c;要集中注意力了&#xff01;接下来就是考验你们观察力的时候啦&#xff01;快跟着小妹儿看一下&#xff0c;一个样式到底能有多少种玩法&#xff1f;文中使用工具为公众号编辑器-小蚂蚁编辑器。1、添加/删除背景编辑器里的内容样式是可以增加或者删除背…

LInux:shell 彩色进度条实现(有图有代码有真相!!!)

一、进度条原理&#xff08;以前的博客详细讲述过&#xff09;&#xff1a;http://blog.csdn.net/sharp_up/article/details/55506555 二、颜色设置 // 字体颜范围(前景颜色):30~39 30:黑 31:红 32:绿 33:黄 34:蓝色 35:紫色 36:深绿 37:白色 // 字背景颜色范围(背景颜…

Linux: 系统配置 crond 和 crontab(有图有代码有真相!!!)

1、相关概述 linux下工作调度的种类有&#xff1a;at , cron 一种是例行性的&#xff0c;就是每隔一定的周期来办某事。 一种是突发性的&#xff0c;就是做完这一次没有以后。 crontab这个命令所设置的工作将会一直循环进行下去&#xff0c;循环的时间可以是分钟、小时、…

LInux:shell 命令:字符串截取

1、cut命令截取 使用说明 cut 命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段写至标准输出。 如果不指定 File 参数&#xff0c;cut 命令将读取标准输入。必须指定 -b、-c 或 -f 标志之一。 主要参数 -b &#xff1a;以字节为单位进行分割。这些字节位置将忽…

smart700iev3 程序下载设置_分享一款Aira2下载工具

Qdown&#xff0c;一款新的Aria2下载器&#xff0c;Aria2是一个命令行的下载器&#xff0c;非常强大&#xff0c;本软件套壳了Aria2&#xff0c;并且制作了界面版本&#xff0c;使用体验不错。Qdown是一款基于Aria2的Windows文件下载器&#xff0c;几乎支持现阶段所有的下载协议…

引用js_js值和引用

值和引用在许多编程语言中&#xff0c;赋值和参数传递可以通过值复制或者引用复制来完成&#xff0c;这取决于我们使用什么语法。例如&#xff0c;在 C 中如果要向函数传递一个数字并在函数中更改它的值&#xff0c;就可以这样来声明参 数 int& myNum&#xff0c;即如果传递…

]数据结构:单链表之判断两个链表是否相交及求交点(带环、不带环)

1、判断两个链表是否相交&#xff0c;若相交&#xff0c;求交点。&#xff08;假设链表不带环&#xff09; 两个指针同时指向两个链表&#xff0c;分别依次往后遍历链表到最后一个节点&#xff0c;如指针的值相同&#xff08;即节点地址相同&#xff09;&#xff0c;反之没有交…

某月某日前包括当天吗_10月26日,你真的理解了导数的定义吗?(答思考题送猫王小音箱)...

点击并关注上方“鸡汤斋”&#xff0c;与斋主一起成长特别说明&#xff1a;公众号的“一天一题”都是从历年期中、期末&#xff0c;以及各个高等学校或者国家统一的考研试题中抽取的题目进行的详细讲解。如果您每天在固定的时间(无聊时、吃饭时、睡觉前、早上起床前、“吃鸡”前…