转自https://blog.csdn.net/hubi0952/article/details/8045094
线程池的基本原理
在传统的服务器结构中,常用一个总的线程监听有没有新的客户端连接服务器。每当有一个新的客户端连接就开启一个新的线程处理这个客户端的信息,这个线程只服务于这个用户,当客户端和服务器关闭连接后服务器端就销毁这个线程。
当服务器频繁的有客户端连接的时候就要频繁的开辟与销毁线程极大的占用了系统的资源。在大量用户的情况下开辟和销毁线程将浪费大量的时间和资源。线程池提供了一个解决外部大量用户与服务器有限资源的矛盾。线程池和传统的一个用户对应一个线程的处理方法不同,它的基本思想就是在程序开始时就在内存中开辟一些线程,线程的数目是固定的,它们独自形成一个类,屏蔽了对外的操作,而服务器只需要将数据包交给线程池就可以了。当有新的客户请求到达时,不是新创建一个线程为其服务,而是从线程池中选择一个空闲的线程为新的客户请求服务,服务完毕后线程进入空闲线程池中。如果没有线程空闲你的话就将数据报暂时积累,等待线程池内有线程空闲以后再进行处理。
通过对多个任务重用已经存在的线程对象,降低了对线程对象的创建和销毁的开销。当客户请求时,由于线程对象已经存在,可以提高请求时间。
线程池的组成部分:
1、线程管理器:用于创建并管理线程池
2、工作线程:线程池中实际执行任务的线程。在初始化线程时会预先创建好固定数目的线程在线程池中,这些初始化的线程一般处于空闲状态(阻塞(睡眠)),不占用CPU,占用较小的内存空间。
3、每个任务必须实现的接口,当线程池中的任务队列(任务链表)中有可执行任务时,被空闲的工作线程调用执行(线程的闲与忙是通过互斥量实现的)。把任务抽象出来形成接口,可做到线程距具体的任务无关。
4、任务队列:用来存放没有处理的任务,提供一种缓冲机制,实现这种结构有好几种方法,常用的有队列,利用队列的先进先出原理。
什么时候需要线程池?
如果一个应用需要频繁的创建和销毁线程,而任务执行的时间又非常短,这样线程创建和销毁带来的开销就不容忽视。这时候就需要线程池了。如果线程创建的和销毁的时间相比任务执行时间可以忽略不计,则没有必要使用线程池。
在Linux系统下用C语言创建一个线程池。线程池会维护一个任务链表(每个CThread_worker结构就是一个任务)。
任务队列的结构和线程池的结构
/*
*线程池里所有运行和等待的队列都是一个CThread_worker结构
*由于所有的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;
/*是否销毁线程池*/
int shutdown;
/*线程ID,使用堆空间来分配内存*/
pthread_t *threadid;
/*线程池中线程的数目*/
int max_thread_num;
/*当前等待队列的任务数目*/
int cur_queue_size;
}CThread_pool;
pool_init()(线程池初始化函数预先创建好max_thread_num个线程),每个线程执行thread_routine()函数。
pool_init()实现
void pool_init(int max_thread_num)
{
pool = (CThread_pool *)malloc(sizeof(CThread_pool));
if(pool == NULL)
{
printf("pool_init1\n");
exit(-1);
}
/*初始化线程互斥锁和条件变量*/
pthread_mutex_init(&(pool->queue_lock), NULL);
pthread_cond_init(&(pool->queue_ready), NULL);
pool->queue_head = NULL;
pool->shutdow = 0;
pool->max_thread_num = max_thread_num;
pool->threadid = (pthread *)malloc(sizeof(pthread) * max_thread_num);
if(pool->threadid == NULL)
{
printf("pool_init2\n");
exit(-1);
}
/*初始化任务队列为0*/
pool->cur_queue_size = 0;
int i = 0;
/*创建max_thread_num数目的线程*/
for(i-0; i<max_thread_num; i++)
{
pthread_create(&pool->threadid[i], NULL, thread_routine, NULL)
}
}
thread_routine的实现(每个线程都执行的函数)
void *thread_routine(void *arg)
{
printf("starting thread 0x%x\n", pthread_self());
while(1)
{
/*因为线程中访问到临界资源(任意时刻只允许一个线程访问的资源),所以要上锁*/
pthread_mutex_lock(&pool->queue_lock);
/*如果任务等待队列为空则线程阻塞,使用了条件变量*/
while(pool->cur_queue_size == 0 && !pool->shutdown)
{
/*pthread_cond_wait是一个原子操作,等待前会解锁,唤醒后会加锁*/
printf("thread 0x%x is waiting\n", pthread_self());
pthread_cond_wait(&(pool->queue_ready), &(pool->queue_lock));
}
if(shutdown)
{
/*遇到break,continue,return等跳转语句,千万不要忘记先解锁*/
pthread_mutex_unlock(&(pool->queue_lock));
printf("thread x%x will exit\n", phread_self());
pthread_exit();
}
printf("thread 0x%x is starting to work\n", pthread_self());
/*从任务队列中取出任务*/
CThread_worker *worker = pool->queue_head;
pool->queue_head = worker->next;
pool->cur_queue_size--;
/*访问临界资源结束,要解锁,以便其他线程访问*/
pthread_mutex_unlock(&(pool->queue_lock));
/*执行任务等待队列中的任务*/
worker->process(worker->arg);
free(worker);
worker = NULL;
}
pthread_exit(NULL);
}
pool_add_worker()函数向线程池的任务队列中加入一个任务,加入后通过调用pthread_cond_signal (&(pool->queue_ready))唤醒一个处于阻塞状态的线程(如果有的话)
void pool_add_worker(void *(*process)(void *arg), void *arg)
{
//为新的任务分配内存,然后添加到任务队列中
CThread_woker *newwork = (CThread_worker *)malloc(sizeof(CThread_worker));
if(newwork == NULL)
{
printf("pool_add_worker\n");
exit(-1);
}
newwork->process = process;
newwork->arg = arg;
newwork->next = NULL;
/*访问到临界资源要上锁*/
pthread_mutex_lock(&(pool->queue_lock));
/*将新任务插入到队尾*/
CThread_worker *temp = pool->queue_head;
if(temp == NULL)
{
pool->queue_head = newwork;
}
else
{
while(temp->next != NULL)
{
temp = temp->next;
}
temp->next = newwork;
}
/*任务等待队列的任务数加1*/
pool->cur_queue_size++;
/*解锁*/
pthread_mutex_unlock(&(pool->queue_lock));
/*发信号唤醒任意一个空闲的线程去处理新加入的任务*/
pthread_cond_signal(&(pool->queue_ready));
}
pool_destroy ()函数用于销毁线程池,线程池任务链表中的任务不会再被执行,但是正在运行的线程会一直把任务运行完后再退出。
void pool_destory()
{
if(pool->shutdown)
{
return -1; /*防止两次调用*/
}
pool->shutdown = 1;
/*唤醒所有等待的线程*/
pthread_cond_broadcast(&(pool->queue_ready));
/*阻塞等待线程退出,否则子线程先退出,主线程没有回收就变成僵尸线程了*/
int i;
for(i=0; i<pool->max_thread_num; i++)
{
pthread_join(pool->threadid[i], NULL);
}
free(pool->threadid);
pool->threadid = NULL;
/*销毁等待队列*/
CThread_worker *temp;
while(pool->queue_head != NULL)
{
temp = pool->queue_head;
pool->queue_head = temp->next;
free(temp);
}
/*销毁条件互斥锁和条件变量*/
pthread_mutex_destory(&(pool->queue_lock));
pthread_cond_destory(&(pool->queue_ready));
free(pool);
/*销毁后指针置空*/
pool = NULL;
return 0;
}
测试函数
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)
{
pool_init (5);/*线程池中最多三个活动线程*/
/*连续向池中投入10个任务*/
int *workingnum = (int *) malloc (sizeof (int) * 10);
int i;
for (i = 0; i < 10; i++)
{
workingnum[i] = i;
pool_add_worker (myprocess, &workingnum[i]);
}
/*等待所有任务完成*/
sleep (5);
/*销毁线程池*/
pool_destroy ();
free (workingnum);
return 0;
}
/*----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
线程池的目的是为了减少线程创建、销毁所带来的代价,当有非常多的任务需要独立的线程去做的时候,可以使用线程池,从线程池中唤醒一个空闲(睡眠状态)线程来处理一个个的任务。
线程池中的每个子线程都是等价的。用线程信号量来控制子线程和任务的分配问题。设置一个信号量来表示任务队列中的任务资源,每个子线程都会处于死循环中,每轮循环首先等待一个任务资源信号量,当等到之后,互斥的从任务队列中摘取一个任务节点。任务节点中记录着该任务所要执行的函数指针及其参数。之后子线程开始执行该任务。执行完后释放一个信号量并进去下一轮循环。当信号量小于1(没有信号量)时,子线程将会阻塞。
因此一个任务由哪一个线程执行,要看那个线程能够获取到对应的信号量资源。
具体实现:
任务队列由双向链表构造,每个节点包含一个任务的函数指针和参数指针。
一般一个简单的线程池有下列组件:
1、线程池管理器(用于创建并管理线程池)
2、工作线程(线程池中的线程)
3、任务接口(task,每个任务必须实现的接口,以供工作线程调度任务的执行)
4、任务队列(用于存放没有处理的任务。提供一种缓冲机制)
线程池工作的基本逻辑:
1.首先 线程池初始化时 会创建出很多条线程,但他们没任务可执行时,就会调用条件变量cond,让自己沉睡。因此线程池一被创建,就躺着很多条沉睡的线程。
2.线程的执行函数中,有个while(1)循环,让线程有任务时执行任务,没任务时就调用pthread_cond_wait()来沉睡。
3.当有任务加入线程池的任务列表时,会通过调用pthread_cond_signal()来唤醒一条线程(add_task()函数),然后线程执行完就继续执行下一条任务或沉睡。
4.当线程池中的 shundown变量变成true时,便会调用pthread_cond_broadcase()唤醒所有沉睡的线程,使线程们自己退出