开发环境:CentOS 6.4
语言: C
编译器:gcc
在服务器程序中,经常会采用为每个客户创建一个线程去处理客户请求;
问题:假设处理客户请求的时间不长,当大量客户陆续接入的时候,那么服务器对每个客户都将做这样的处理:创建线程 -> 处理客户请求 -> 销毁线程;由于每个客户请求的处理时间都不长,而创建线程和销毁线程需要花费较大的系统开销,所以当大量客户接入的时候服务器将花费大量的时间在创建和销毁线程上面,从而影响服务器性能。
解决方法:通常对于上面这种情况,服务器会预先派生一定数目的线程(创建一个线程池),并让这些线程阻塞在某个条件上,如等待客户接入,当有客户接入的时候,就从线程池中唤醒一个线程去处理客户的请求,处理完成后,该线程回到原来的阻塞状态继续等待客户的接入;这样服务器的工作就是:创建一批线程并等待客户接入 -> 客户接入唤醒一个线程处理客户请求,处理完成后线程回到等待状态 -> …… -> 全部处理完成销毁所有线程;
例如,一个服务器每天接入的客户有10000个,但是同时在线的最多只有5000个,如果不采用线程池,服务器处理10000个客户将创建10000个线程去处理,而采用线程池,则开始创建5000个线程的线程池,每当客户接入时就从线程池中取出一个线程处理客户请求,处理完成后将线程放回线程池,当所有客户处理完成后销毁线程池,这样就大大节省了服务器创建和销毁线程的时间。
线程池的主要组成部分由下面四个:
1. 线程池管理器,用于创建和管理线程池的;
2. 工作线程,即线程池中的线程,用于处理任务;
3. 任务接口,提供处理任务的接口,供工作线程调度执行任务;
4. 任务队列,存放待处理的任务,工作线程从中取出任务并执行,相当于提供一种任务缓冲的机制;
下面给出对应的实现,代码参考了网上很多的写法,以及Gaea中的写法(github.com/58codeGaea/client/c/client/src/threadpool):
先给出任务接口:
typedef struct task {
void *(*func)(void *);
void *arg;
} task_t;
其中func为待处理的任务,arg为函数参数;
任务队列的实现:
typedef struct queue {
int size;
int cnt;
int front, rear;
task_t *qa;
} queue_t;
queue_t *create_queue(int size);
void destroy_queue(queue_t *q);
int isqfull(queue_t *q);
int isqempty(queue_t *q);
void enqueue(queue_t *q, const task_t *tk);
void dequeue(queue_t *q, task_t *tk);//出队并通过tk返回
具体实现方式采用环形数组,主要实现创建队列、销毁队列、入队和出队的操作;
线程池结构以及相应的操作:
typedef struct thread_pool {
int onflg; //线程池开启标志,1=on 0=off
int maxthrd, maxtq; //线程池最大线程数和任务队列最大任务数
pthread_t *thrds; //线程池id组
queue_t *tq; //任务队列
pthread_mutex_t tq_lock; //任务队列锁
pthread_cond_t tq_ready; //任务队列准备就绪
} tpool_t;
tpool_t *init_tpool(int maxthrd, int maxtq);
void destroy_tpool(tpool_t *tpl);
int is_tpool_on(tpool_t *tpl);
void tpool_add_task(tpool_t *tpl, task_t *tk);
主要实现:
创建并初始化线程池:开启线程池标志置位,创建一组线程,创建一个任务队列,并初始化任务队列相关的锁和条件变量;
向线程池中添加任务:将任务添加到任务队列中并发送一个信号表示有任务准备就绪;处于等待状态的工作线程被信号唤醒后,将从任务队列中取出任务并执行;
销毁线程池;
工作线程:
static void *thread_routine(void *arg);
工作线程的主要工作为处理任务,当任务队列不为空的时候,工作线程直接从队列头取出一个任务并执行;当任务队列为空的时候,工作线程将阻塞直到有任务添加进来;
init_tpool:
//创建线程池并初始化
//参数:线程池最大线程数、任务队列最大任务数
//返回:线程池指针
tpool_t *init_tpool(int maxthrd, int maxtq)
{
tpool_t *tpl;
tpl = malloc(sizeof(*tpl));
if (NULL == tpl)
return NULL;
tpl->onflg = 1;
tpl->maxthrd = maxthrd;
tpl->maxtq = maxtq;
tpl->thrds = malloc(sizeof(pthread_t) * maxthrd);
if (NULL == tpl->thrds)
return NULL;
tpl->tq = create_queue(maxtq);
if (NULL == tpl->tq)
return NULL;
if (0 != pthread_mutex_init(&tpl->tq_lock, NULL)) {
perror("pthread_mutex_init");
return NULL;
}
if (0 != pthread_cond_init(&tpl->tq_ready, NULL)) {
perror("pthread_cond_init");
return NULL;
}
int i;
for (i = 0; i < maxthrd; i ++) {
if (0 != pthread_create(&tpl->thrds[i], NULL, thread_routine, (void *)tpl)) {
perror("pthread_create");
return NULL;
}
}
return tpl;
}
destroy_tpool:
//销毁线程池
//参数:线程池指针
//返回:void
void destroy_tpool(tpool_t *tpl)
{
if (NULL == tpl)
return;
if (is_tpool_on(tpl)) {
tpl->onflg = 0;
pthread_mutex_lock(&tpl->tq_lock);
if (0 != pthread_cond_broadcast(&tpl->tq_ready)) {
perror("pthread_cond_broadcast");
return;
}
pthread_mutex_unlock(&tpl->tq_lock);
int i;
for (i = 0; i < tpl->maxthrd; i ++) {
pthread_join(tpl->thrds[i], NULL);
}
free(tpl->thrds);
destroy_queue(tpl->tq);
pthread_cond_destroy(&tpl->tq_ready);
pthread_mutex_destroy(&tpl->tq_lock);
free(tpl);
tpl = NULL;
}
}
tpool_add_task:
//向任务队列中添加任务
//参数:线程池指针、任务指针
//返回:void
void tpool_add_task(tpool_t *tpl, task_t *tk)
{
if (NULL == tpl)
return;
pthread_mutex_lock(&tpl->tq_lock);
if (isqfull(tpl->tq)) {
printf("##task queue full..,can't add task\n");
pthread_mutex_unlock(&tpl->tq_lock);
return;
}
enqueue(tpl->tq, tk);
pthread_cond_signal(&tpl->tq_ready);
pthread_mutex_unlock(&tpl->tq_lock);
}
工作线程:
static void *thread_routine(void *arg)
{
tpool_t *tpl = arg;
task_t ttask;
if (NULL == tpl)
return NULL;
while (1) {
pthread_mutex_lock(&tpl->tq_lock);
while (isqempty(tpl->tq) && is_tpool_on(tpl)) {
pthread_cond_wait(&tpl->tq_ready, &tpl->tq_lock);
}
if (!is_tpool_on(tpl)) {
pthread_mutex_unlock(&tpl->tq_lock);
pthread_exit(NULL);
}
dequeue(tpl->tq, &ttask);
pthread_mutex_unlock(&tpl->tq_lock);
ttask.func(ttask.arg);
}
return NULL;
}
//判断线程池开启
//参数:线程池指针
//返回:1=on 0=off
int is_tpool_on(tpool_t *tpl)
{
return (1 == tpl->onflg);
}
队列的实现采用环形数组实现,避免占篇幅这里就不贴了;
测试代码:
初始化一个线程池,线程池中的有5个线程,任务队列的长度为12,添加12个任务到线程池,当任务队列为空后销毁线程池;
#include
#include
#include
#include "queue.h"
#include "threadpool.h"
void *print1(void *arg)
{
int *i = arg;
printf("##tid %x @%d \n", pthread_self(), *i);
sleep(2);
printf("##tid %x @%d out~~~\n", pthread_self(), *i);
return NULL;
}
void *print2(void *arg)
{
int *i = arg;
printf("\t\t##tid %x @%d \n", pthread_self(), *i);
sleep(5);
printf("\t\t##tid %x @%d out~~~\n", pthread_self(), *i);
return NULL;
}
int main(int argc, char **argv)
{
int i;
tpool_t *tpl;
task_t tt;
int ta[12];
tpl = init_tpool(5, 15);
for (i = 0; i < 12; i ++)
ta[i] = i;
for (i = 0; i < 12; i ++) {
if (i % 2)
tt.func = print1;
else
tt.func = print2;
tt.arg = (void *)&ta[i];
tpool_add_task(tpl, &tt);
}
while (!isqempty(tpl->tq));
destroy_tpool(tpl);
return 0;
}
我的makefile:
CC = gcc
CLIBS = -lpthread
sources = $(wildcard *.c)
test:$(sources:.c=.o)
$(CC) -o $@ $^ $(CLIBS)
-include $(sources:.c=.d)
%.d:%.c
set -e; rm -f $@; \
$(CC) -MM $< > $@.$$$$; \
sed 's/\($*\).o[ :]*/\1.o $@:/g' < $@.$$$$ > $@; \
rm -f $@.$$$$
.PHONY:clean
clean:
rm -f test $(sources:.c=.d) $(sources:.c=.o)