linux用C语言创建线程池

一.  线程池的应用描述

     大多数的网络服务器,包括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 }



你可能感兴趣的:(Linux)