1、为什么要使用线程池?
在传统服务器结构中,有一个总的监听线程监听有没有新的用户连接服务器,当每有一个新的用户进入时,服务器就会开启一个新的线程用于处理这个用户的数据包。这个线程只服务于这个用户,当用户与服务器关闭连接以后,服务器就会销毁这个线程。然而频繁的开辟与销毁线程极大的占用了系统的资源。如果有大量的用户请求的情况下,系统为了开辟和销毁线程就会浪费大量的时间和资源。既然有问题的出现就会有相应的解决方案,线程池提供了一个外部用户大量与有限资源的矛盾,用开解决当外部有多个请求时,减少线程的创建和销毁所占用的资源,以此来提高系统资源的利用率。当然如果线程创建和销毁的时间相比于任务执行的时间可以忽略不计的话,就没有必要使用线程池了。
2、使用线程池的优势?
(1)减少在创建和销毁线程上所花的时间以及系统资源的开销
(2)如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”。
(3)提高系统的相应速度。
3、线程池的基本原理
线程池和传统的一个用户对应一个线程的处理方法不同,它的基本思想是在程序开始的时候就在内存中开辟一些线程,现成的数目是固定的,他们独自形成一个类,屏蔽对外的操作,而服务器只需要将数据包交给线程池就可以了。当有新的用户请求到达时,不是新创键一个线程为其服务,而是从所创建的线程中选择一个空闲的线程服务新的客户端,当服务完毕时,线程进入空闲线程池中。如果没有空闲线程的话,就将数据包暂时积累,等待线程池内有空闲的线程以后再进行处理。通过重用一个线程对已存在的任务进行服务,降低了线程对象创建和销毁的开销。当用户请求时,线程已经存在,可以提高请求的响应时间,从而整体的提高了系统的服务效率。
4、 线程池的组成部分
(1)线程管理器:用于创建和管理线程。
(2)工作线程:线程池中实际执行任务的线程。在初始化线程时会预先创建好固定数目的线程在池中,这些初始化的线程一般处于空闲状态,一般不占用CPU,占用较小的内存空间。如果一旦任务队列中有任务添加时,会唤醒处于等待状态的线程来执行新添加的任务。
(3)任务接口:每个任务必须实现的接口(即一个回调函数),当线程池的任务队列中有可执行任务时,被空闲的工作线程调去执行(线程的闲与忙是通过互斥量实现的)把任务抽象出来形成接口,可以做到线程池与具体的任务无关。
(4)任务队列:用来存放没有处理的任务,提供一种缓冲机制,实现这种结构有好几种方法,常用的是队列,主要运用先进先出原理,另外一种是链表之类的数据结构,可以动态的为它分配内存空间,应用中比较灵活。
在传统服务器结构中,有一个总的监听线程监听有没有新的用户连接服务器,当每有一个新的用户进入时,服务器就会开启一个新的线程用于处理这个用户的数据包。这个线程只服务于这个用户,当用户与服务器关闭连接以后,服务器就会销毁这个线程。然而频繁的开辟与销毁线程极大的占用了系统的资源。如果有大量的用户请求的情况下,系统为了开辟和销毁线程就会浪费大量的时间和资源。既然有问题的出现就会有相应的解决方案,线程池提供了一个外部用户大量与有限资源的矛盾,用开解决当外部有多个请求时,减少线程的创建和销毁所占用的资源,以此来提高系统资源的利用率。当然如果线程创建和销毁的时间相比于任务执行的时间可以忽略不计的话,就没有必要使用线程池了。
2、使用线程池的优势?
(1)减少在创建和销毁线程上所花的时间以及系统资源的开销
(2)如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”。
(3)提高系统的相应速度。
3、线程池的基本原理
线程池和传统的一个用户对应一个线程的处理方法不同,它的基本思想是在程序开始的时候就在内存中开辟一些线程,现成的数目是固定的,他们独自形成一个类,屏蔽对外的操作,而服务器只需要将数据包交给线程池就可以了。当有新的用户请求到达时,不是新创键一个线程为其服务,而是从所创建的线程中选择一个空闲的线程服务新的客户端,当服务完毕时,线程进入空闲线程池中。如果没有空闲线程的话,就将数据包暂时积累,等待线程池内有空闲的线程以后再进行处理。通过重用一个线程对已存在的任务进行服务,降低了线程对象创建和销毁的开销。当用户请求时,线程已经存在,可以提高请求的响应时间,从而整体的提高了系统的服务效率。
4、 线程池的组成部分
(1)线程管理器:用于创建和管理线程。
(2)工作线程:线程池中实际执行任务的线程。在初始化线程时会预先创建好固定数目的线程在池中,这些初始化的线程一般处于空闲状态,一般不占用CPU,占用较小的内存空间。如果一旦任务队列中有任务添加时,会唤醒处于等待状态的线程来执行新添加的任务。
(3)任务接口:每个任务必须实现的接口(即一个回调函数),当线程池的任务队列中有可执行任务时,被空闲的工作线程调去执行(线程的闲与忙是通过互斥量实现的)把任务抽象出来形成接口,可以做到线程池与具体的任务无关。
(4)任务队列:用来存放没有处理的任务,提供一种缓冲机制,实现这种结构有好几种方法,常用的是队列,主要运用先进先出原理,另外一种是链表之类的数据结构,可以动态的为它分配内存空间,应用中比较灵活。
下面是自己对线程池理解时画的一个建议的图:
5、一个线程池的例子。
#include <unistd.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <pthread.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;
/*是否销毁线程池*/
int shutdown;
pthread_t *threadid;
/*线程池中允许的活动线程数目*/
int max_thread_num;
/*当前等待队列的任务数目*/
int cur_queue_size;
} CThread_pool;
int pool_add_worker(void *(process)(void *arg), void *arg);
void *thread_routine(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->cur_queue_size = 0;
pool->shutdown = 0;
pool->threadid = (pthread_t *)malloc(max_thread_num * sizeof(pthread_t));
int i = 0;
for (i = 0; i < max_thread_num; i++)
{
pthread_create(&(pool->threadid[i]), NULL, thread_routine, NULL);
}
}
void *thread_routine(void *arg)
{
printf("strating thread %u\n", pthread_self());
while(1)
{
pthread_mutex_lock(&(pool->queue_lock));
/*如果等待线程为0并且不销毁线程池,则处于阻塞状态;
* 注意pthread_cond_wait是一个原子操作,等待前会解锁,
* 唤醒后会加锁
*/
while(pool->cur_queue_size == 0 && !pool->shutdown)
{
printf("thread %u is waiting\n", pthread_self());
pthread_cond_wait(&(pool->queue_ready), &(pool->queue_lock));
}
/*线程池要销毁*/
if (pool->shutdown)
{
/*遇到break, continue, return等跳转语句,千万不要忘记先解锁*/
pthread_mutex_unlock(&(pool->queue_lock));
printf("thread %u will exit.\n", pthread_self());
pthread_exit(NULL);
}
printf("thread %u is starting to work\n", pthread_self());
//assert用于调试
assert(pool->cur_queue_size != 0);
assert(pool->queue_head != NULL);
/*等待队列长度减去1,并取出链表中的头元素*/
pool->cur_queue_size--;
CThread_worker *worker = pool->queue_head;
pool->queue_head = worker->next;
pthread_mutex_unlock(&(pool->queue_lock));
/*调用回调函数,执行任务*/
(*(worker->process))(worker->arg);
free(worker);
worker = NULL;
}
pthread_exit(NULL);
}
/*向线程池中添加任务*/
int pool_add_worker(void *(*process)(void *arg), void *arg)
{
printf("add worker is %d\n", *(int *)arg);
/*构造一个新任务*/
CThread_worker *newworking = (CThread_worker *)malloc(sizeof(CThread_worker));
newworking->process = process;
newworking->arg = arg;
newworking->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 = newworking;
}
else
{
pool->queue_head = newworking;
}
assert(pool->queue_head != NULL);
pool->cur_queue_size++;
pthread_mutex_unlock(&(pool->queue_lock));
/*等待队列中有任务了,唤醒一个等待线程,
* 注意所有的线程都在忙碌,这句话就不会起作用
*/
pthread_cond_signal(&(pool->queue_ready));
return 0;
}
void *myprocess(void *arg)
{
printf("thread is %u, working on task %d\n", pthread_self(), *(int *)arg);
sleep(1); /*休息1秒,延长任务执行时间*/
return NULL;
}
/*销毁线程池,等待队列中的任务不会再执行,但是正在运行的线程会一直把任务运行完后退出*/
int pool_destroy()
{
if (pool->shutdown)
return -1; /*防止2次调用*/
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);
/*销毁等待队列*/
CThread_worker *head = NULL;
while (pool->queue_head != NULL)
{
head = pool->queue_head;
pool->queue_head = pool->queue_head->next;
free(head);
}
/*条件变量和互斥量也要销毁*/
pthread_mutex_destroy(&(pool->queue_lock));
pthread_cond_destroy(&(pool->queue_ready));
free(pool);
/*销毁指针置空*/
pool = NULL;
printf("thread pool destroy finish!\n");
return 0;
}
int main(int argc, char *argv[])
{
pool_init(3); /*线程池中最多有3个线程*/
/*连续向池中投放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;
}
运行结果:
add worker is 0
add worker is 1
add worker is 2
add worker is 3
add worker is 4
add worker is 5
add worker is 6
add worker is 7
add worker is 8
add worker is 9
strating thread 3067124544
thread 3067124544 is starting to work
thread is 3067124544, working on task 0
strating thread 3075517248
thread 3075517248 is starting to work
thread is 3075517248, working on task 1
strating thread 3058731840
thread 3058731840 is starting to work
thread is 3058731840, working on task 2
thread 3075517248 is starting to work
thread is 3075517248, working on task 3
thread 3067124544 is starting to work
thread is 3067124544, working on task 4
thread 3058731840 is starting to work
thread is 3058731840, working on task 5
thread 3075517248 is starting to work
thread is 3075517248, working on task 6
thread 3067124544 is starting to work
thread is 3067124544, working on task 7
thread 3058731840 is starting to work
thread is 3058731840, working on task 8
thread 3075517248 is starting to work
thread is 3075517248, working on task 9
thread 3067124544 is waiting
thread 3058731840 is waiting
thread 3075517248 is waiting
thread 3067124544 will exit.
thread 3058731840 will exit.
thread 3075517248 will exit.
thread pool destroy finish!