池
由于服务器的硬件资源“充裕”,那么提高服务器性能的一个很直接的方法就是以空间换时间,即“浪费”服务器的硬件资源,以换取其运行效率。这就是池的概念。
池是一组资源的集合,这组资源在服务器启动之初就被创建并初始化,这称为静态资源分配。
当服务器进入正式运行阶段,即开始处理客户请求的时候,如果它需要相关的资源,就可以直接从池中获取,无需动态分配。很显然,直接从池中取得所需资源比动态分配资源的速度要快得多,因为分配系统资源的系统调用都是很耗时的。
当服务器处理完一个客户连接后,可以把相关的资源放回池中,无需执行系统调用来释放资源。从最终效果来看,池相当于服务器管理系统资源的应用设施,它避免了服务器对内核的频繁访问。提高了效率。
池可以分为很多种,常见的有进程池,线城池,内存池。
内存池
内存池是一种内存分配方式。通常我们直接使用new、malloc等系统调用申请分配内存,这样做的缺点在于:由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。
内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升。
线程池
在面向对象程序编程中,对象的创建与析构都是一个较为复杂的过程,较费时间,所以为了提高程序的运行效率尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。
所以我们可以创建一个线程池,预先构造一些线程,要用的时候就直接调用,用完之后再把线程归还给线程池,省下创建删除线程的时间,不过当然就需要额外的开销了。
线程池的应用
线程池主要用于
1、需要大量的线程来完成任务,且完成任务的时间比较短。
WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。
因为单个任务小,而任务数量巨大,一个热门网站的点击次数会很多。
但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
2、对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
3、接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。
线程池的好处在于减少了频繁创建线程和销毁线程的时间,提高了效率。
线程池的实现示例
Linux系统下用C语言创建的一个线程池。简单的来说,在程序初始的时候创建一批线程放入线程池中,同时需要维护一组任务worker,按照某种分配策略让线程竞争任务。当没有任务需要执行的时候,所有线程都会阻塞;当任务数量过多的时候,任务在队列中等待。
其中,任务队列使用链表的方式实现,同时,需要用到 锁 和 条件变量 来实现互斥和共享。具体细节简单如下:
1. pool_init()函数预先创建好若干个个线程,每个线程执thread_routine ()函数。该函数中
while (pool->cur_queue_size == 0) {
pthread_cond_wait (&(pool->queue_ready),&(pool->queue_lock));
}
表示如果任务链表中没有任务,则该线程出于阻塞等待状态。否则从队列中取出任务并执行。
2. pool_add_worker()函数向线程池的任务链表中加入一个任务,加入后通过调用pthread_cond_signal (&(pool->queue_ready))唤醒一个出于阻塞状态的线程(如果有的话)。
3. pool_destroy ()函数用于销毁线程池,线程池任务链表中的任务不会再被执行,但是正在运行的线程会一直把任务运行完后再退出。
具体代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <assert.h>
#include <string.h>
#include <sys/types.h>
// 线程池里所有运行和等待的任务都是一个CThread_worker, 由于所有任务都在链表里,所以是一个链表结构
typedef struct worker {
void *(*process)(void *arg); // 回调函数,任务运行时会调用此函数,注意也可声明成其它形式
void *arg; // 回调函数的参数
struct worker *next;
} CThread_worker;
// 线程池结构
typedef struct {
pthread_mutex_t queue_lock;
pthread_cond_t queue_ready;
// 链表结构,线程池中所有等待任务
CThread_worker *queue_head;
// 线程id数组
pthread_t *threadid;
// 是否销毁线程池
int shutdown;
// 线程池中线程数目
int max_thread_num;
// 等待队列的任务数目
int cur_queue_size;
} CThread_pool;
/****** 线程池主要功能 ******/
// 初始化线程池
void pool_init(int max_thread_num);
// 清除任务
void pool_destroy();
// 往任务队列中添加任务
int pool_add_worker(void *(*process)(void *arg), void *arg);
// 线程体执行的操作
void *thread_routine(void *arg);
// 示例程序
void *myprocess(void *arg);
static CThread_pool *pool = NULL;
void pool_init(int max_thread_num) {
pool = (CThread_pool *) malloc (sizeof (CThread_pool));
pthread_mutex_init(&(pool->queue_lock), NULL);
pthread_cond_init(&(pool->queue_ready), NULL);
pool->queue_head = NULL;
pool->max_thread_num = max_thread_num;
pool->shutdown = 0;
pool->threadid = (pthread_t *) malloc(sizeof(pthread_t) * max_thread_num);
int i;
for (i = 0; i < max_thread_num; i++) {
pthread_create(&(pool->threadid[i]), NULL, thread_routine, NULL);
}
}
// 销毁线程池,等待队列中的任务不会再被执行,但是正在运行的线程会一直把任务运行完后再退出
void pool_destroy() {
if (pool->shutdown)
return; // 防止多次调用
pool->shutdown = 1;
// 通知所有的线程,线程池要销毁
pthread_cond_broadcast (&(pool->queue_ready));
// 释放线程池中的threadid
int i = 0;
for (i = 0; i < pool->max_thread_num; i++)
pthread_join(pool->threadid[i], NULL);
free(pool->threadid);
// 释放所有的等待队列
while (pool->queue_head != NULL) {
CThread_worker *head= pool->queue_head;
pool->queue_head = pool->queue_head ->next;
free(head);
}
// 销毁lock和condition
pthread_mutex_destroy(&(pool->queue_lock));
pthread_cond_destroy(&(pool->queue_ready));
// 最后free掉pool,销毁后将指针置空
// 销毁后指针置空是个好习惯
free(pool);
pool = NULL;
return;
}
// 向线程池中加入任务
int pool_add_worker(void *(*process)(void *arg), void *arg) {
// 构造一个新任务
CThread_worker *newworker = (CThread_worker *) malloc (sizeof(CThread_worker));
newworker->process = process;
newworker->arg = arg;
newworker->next = NULL; // 不要忘记置空
// 将任务加入到等待队列中
pthread_mutex_lock(&(pool->queue_lock));
CThread_worker *member = pool->queue_head;
if (member != NULL) {
while (member->next != NULL)
member = member->next;
member->next = newworker;
}
else
pool->queue_head = newworker;
assert(pool->queue_head != NULL);
pool->cur_queue_size++;
pthread_mutex_unlock(&(pool->queue_lock));
// 等待队列中有任务了,唤醒一个等待线程, 注意如果所有线程都在忙碌,这句没有任何作用
pthread_cond_signal(&(pool->queue_ready));
return 0;
}
// 线程体执行的操作
void *thread_routine(void *arg) {
printf("starting thread 0x%x\n", pthread_self());
while (1) {
// 因为要操作worker队列,因此先要加锁
pthread_mutex_lock(&(pool->queue_lock));
// 如果等待队列为0并且不销毁线程池,则处于阻塞状态;
// 注意pthread_cond_wait是一个原子操作,等待时会释放锁,唤醒后会加锁
while (pool->cur_queue_size == 0 && !pool->shutdown) {
// 遇到break,continue,return等跳转语句,千万不要忘记先解锁
printf("thread 0x%x is waiting\n", pthread_self());
pthread_cond_wait(&(pool->queue_ready), &(pool->queue_lock));
}
// 销毁线程池
if (pool->shutdown == 1) {
pthread_mutex_unlock(&(pool->queue_lock));
printf("thread 0x%x will exit\n", pthread_self());
pthread_exit(NULL);
}
// 执行任务
// assert是调试的好帮手
assert(pool->cur_queue_size != 0);
assert(pool->queue_head != NULL);
// 等待队列长度减去1,并取出链表中的头部元素,操作完毕释放锁
printf("thread 0x%x is starting to work\n", pthread_self());
pool->cur_queue_size--;
CThread_worker *worker = pool->queue_head;
pool->queue_head = pool->queue_head->next;
pthread_mutex_unlock(&(pool->queue_lock));
// 调用回调函数,执行任务
(*(worker->process))(worker->arg);
free(worker);
worker = NULL;
}
pthread_exit(NULL); // 这一句应该是不可达的
}
// 测试代码
void *myprocess(void *arg) {
printf("threadid is 0x%x, working on task %d\n", pthread_self(), *(int *)arg);
sleep(1); // 休息一秒,延长任务的执行时间
return NULL;
}
int main(int argc, char **argv) {
// 初始化线程池,创建3个线程
pool_init(3);
// 往任务队列中加入10个任务
int *workingnum = (int *) malloc (sizeof(int) * 10);
int i = 0;
for (i = 0; i < 10; i++) {
workingnum[i] = i;
pool_add_worker(myprocess, &workingnum[i]);
}
// 等待所有的任务执行完毕
sleep(5);
// 销毁线程池
pool_destroy();
free(workingnum);
return 0;
}
结果如下: