一、问题引入
在前面编写多进程、多线程服务器时通过动态创建子进程和子线程来实现并发服务器,这样做有以下缺点:
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
#include
#include
#include
#include
#include
/* 要执行的任务链表 */
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;
}