一. 线程池的应用描述
大多数的网络服务器,包括Web服务器都具有一个特点,就是单位时间内必须处理数目巨大的连接请求,但是处理时间却是比较短的。在传统的多线程服务器模型中是这样实现的:一旦有个请求到达,就创建一个新的线程,由该线程执行任务,任务执行完毕之后,线程就退出。这就是"即时创建,即时销毁"的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数非常频繁,那么服务器就将处于一个不停的创建线程和销毁线程的状态。这笔开销是不可忽略的,尤其是线程执行的时间非常短的情况。
线程池就是为了解决上述问题的,它的实现原理是这样的:在应用程序启动之后,就马上创建一定数量的线程,放入空闲的队列中。这些空闲线程都是处于阻塞状态,只占一点内存,不占用CPU。当任务到来后,从线程池中选择一个空闲的线程,将任务传入此线程中运行。当所有的线程都处在处理任务的时候,线程池将自动创建一定的数量的新线程,用于处理更多的任务。执行任务完成之后线程并不退出,而是继续在线程池中等待下一次任务。当大部分线程处于阻塞状态时,线程池将自动销毁一部分的线程,回收系统资源。
线程池提供了一个解决外部大量用户与服务器有限资源的矛盾, 线程池和传统的一个用户对应一 个线程的处理方法不同, 它的基本思想就是在程序 开始时就在内存中开辟一些线程, 线程的数目是固定的,他们独自形成一个类, 屏蔽了对外的操作, 而服务器只需要将数据包交给线程池就可以了。当有新的客户请求到达时 , 不是新创建一个线程为其服务 , 而是从“池子”中选择一个空闲的线程为新的客户请求服务 ,服务完毕后 , 线程进入空闲线程池中。
二. 线程池主要包括以下几个组成部分及代码实现:
为了简单,以下我们只关注核心部分。并且线程池是固定大小的,对于线程池的自动扩张和缩减并没有实现。
相关结构定义:
8 /*A task include a function and its parameter. next is used to construct a task queue.*/
9 typedef struct Task{
10 void* (*run_task) (void* arg);
11 void* arg;
12 struct Task* next;
13 }ThreadTask;
14
15 //ThreadPool: manager the threads and task queue.
16 typedef struct Pool{
17 //操作任务队列时,必须加锁。
18 pthread_mutex_t queue_lock;
19 //条件变量,线程等待该变量成立,当任务到来时,需要唤醒线程执行任务。
20 pthread_cond_t queue_ready;
21
22 //Task queue in ThreadPool
23 ThreadTask* queue_head;
24 //The number of waiting task
25 int queue_size;
26
27 //The thread id List
28 pthread_t* threadid_list;
29 //max number of thread in ThreadPool
30 int max_thread_num;
31
32 //describe the pool is shutdowning
33 int shutdown;
34 }ThreadPool;
a)创建线程池
void pool_create(int number){
//主要是初始化变量
40 pool= (ThreadPool*) malloc(sizeof(ThreadPool));
41 pthread_mutex_init(&(pool->queue_lock),NULL);
42 pthread_cond_init(&(pool->queue_ready),NULL);
43 pool->queue_head=NULL;
44 pool->queue_size=0;
45 pool->max_thread_num=number;
46 pool->threadid_list=(pthread_t*) malloc(number*sizeof(pthread_t));
47 int i;
48 for(i=0;ithreadid_list[i]),NULL,thread_runable,NULL);
50 }
51 pool->shutdown=0;
52 }
b)销毁线程池
/*销毁线程池,等待队列中的任务不会再被执行,但是正在运行的线程会一直把任务运行完后再退出*/
77 int pool_destroy(){
78
79 if(pool->shutdown){ return -1;}
80 pool->shutdown=1;
81
82 /*唤醒所有等待线程,线程池要销毁了,让其全部自动退出*/
83 pthread_cond_broadcast(&(pool->queue_ready));
84 /*阻塞等待线程退出,就是要等待其任务他线程都退出,否则就成僵尸了*/
85 int i;
86 for(i=0;imax_thread_num;i++){
87 pthread_join(pool->threadid_list[i],NULL);
88 }
89 //释放线程ID的内存
90 free(pool->threadid_list);
91
92 //销毁等待队列,并回收资源
93 }
c)管理线程池中的任务
用来存放没有处理的任务,提供一种缓冲机制,实现这种结构有好几种方法,采用了链表的数据结构,可以动态的为它分配内存空间,应用中比较灵活,下文中就是用到的链表。这里为了方便,后来的任务反而会先执行,当然可以用队列来使先来的任务先执行。
void pool_add_task(void* (*task) (void*),void* param){
54 //先生成任务的一个结构
55 ThreadTask* nw=(ThreadTask*) malloc(sizeof(ThreadTask));
56 nw->run_task=task;
57 nw->arg=param;
58 nw->next=NULL;
59 //add the task in the waiting task queue
60 //locker
61 if(0!=pthread_mutex_lock(&(pool->queue_lock))) puts("locker queue error");
62 //Here we just add the task in the head
63
64 nw->next=pool->queue_head;
65 pool->queue_head=nw;
66
67 pool->queue_size++;
68
69 if(0!=pthread_mutex_unlock(&(pool->queue_lock))) puts("unlock queue error!");
70 //unlocker
71
72 //awake a thread if has one
73 pthread_cond_signal(&(pool->queue_ready));
74 }
d)管理线程池中的线程
线程池中实际执行任务的线程。在初始化线程时会预先创建好固定数目的线程在池中,这些初始化的线程一般处于空闲状态,一般不占用CPU,占用较小的内存空间。当线程开始执行时,会从任务表中,获得任务,然后执行任务,如果没有任务则继续等待。
void *thread_runable(void* param){
95 printf("starting thread_id is:0x%x\n",pthread_self());
96 while(1){
97 if(pool->shutdown){
98 printf("thread id=0x%x is exit\n",pthread_self());
99 pthread_exit(NULL);
100 }
101 /*首先,需要取出队列中的一个任务。*/
102 //lock
103 pthread_mutex_lock(&(pool->queue_lock));
104 /*如果等待队列为0并且不销毁线程池,则处于阻塞状态; 注意
105 pthread_cond_wait是一个原子操作,等待前会解锁,唤醒后会加锁*/
106 if(pool->queue_head==NULL && !pool->shutdown){
107 printf("thread id=0x%x is waiting\n",pthread_self());
108 pthread_cond_wait(&(pool->queue_ready),&(pool->queue_lock));
109 }
110 if(pool->shutdown){
111 printf("thread id=0x%x is exit\n",pthread_self());
112 pthread_mutex_unlock(&(pool->queue_lock));
113 //unlock
114 pthread_exit(NULL);
115 }
116
117 assert(pool->queue_head!=NULL);
118 assert(pool->queue_size>0);
119
120 /*取出第一个任务执行*/
121 pool->queue_size--;
122 ThreadTask* cur=pool->queue_head;
123 pool->queue_head=pool->queue_head->next;
124
125 pthread_mutex_unlock(&(pool->queue_lock));
126 //unlock
127
128 //running the task.
129 (*(cur->run_task))(cur->arg);
130 free(cur);
131 cur=NULL;
132 }
133 pthread_exit(NULL);
134 }
e)任务接口
每个任务必须实现的接口,当线程池的任务队列中有可执行任务时,被空闲的工作线程调去执行(线程的闲与忙是通过互斥量实现的,把任务抽象出来形成接口,可以做到线程池与具体的任务无关。以下只是实现了一个简单的任务接口,方便做测试。
void* thread_task(void* param){
136 printf("thread id=0x%x is working on the task,param=%d\n",pthread_self(),*((int*)param));
137 sleep(1);
138 return NULL;
139 }
测试代码如下:
int main(int argc,char* argv[]){
141 pool_create(3);
142 int i,num[10];
143 for(i=0;i<10;i++){
144 num[i]=i;
145 pool_add_task(thread_task,&(num[i]));
146 }
147 sleep(10);
148 pool_destroy();
149 return 0;
150 }