前言
线程池的构造是这样的,系统根据线程池初始化的线程数预先分配一块内存给线程池。当有任务进来的时候,直接将事先创建好的线程分配给任务。如果没有可用的空闲线程了,就令任务在任务队列中等待。 当任务请求过多时,很好的避免了为每个任务都分配一个线程,导致资源消耗过多最终影响全体任务性能的情况。同时减少了线程的频繁创建和销毁,毕竟创建和销毁线程也需要cpu资源和消耗时间的。
线程池的结构
线程池的构建基本都是由线程池控制器(ThreadPool Controller)以及任务节点(Task)组成。假如用两个结构体表示的话,代码是这样的
typedef struct Task{
void *(*TaskFunc) (void *arg);//这个指向的是需要执行的任务函数
void *arg;//这是对应的任务函数的参数
struct Task *next;//毕竟要存在于任务队列中,所以有个next指向下一个任务节点
};
typedef struct ThreadpollCtrl{
Task *queue_head; //指向任务队列的头节点
int total_thread_nums;//创建的线程数量
int cur_task_nums;// 正在执行任务的线程数量
int max_task_nums;//线程池最大容纳线程数
int queue_size;//任务等待队列中的任务数量
pthread_t *thread_id;//线程指针
pthread_mutex_t thread_mutex; //线程互斥锁
pthread_cond_t thread_cond; //线程条件变量
int shutdown;//是否关闭线程池的flag标志
};
当创建线程池的时候,我们要给先设定好对应的参数,比如最大可容纳线程数,初始化多少个空闲线程等。如上图初始化了10个线程,最大可容纳的线程数可以为10,也可以设置比10大。
运作过程
1.线程池管理线程的方式:
(1)当线程池被创建的时候,它会初始化一定数量的空闲线程,这些空闲线程等待这用户投递来的任务。
(2)当用户投递新的任务的时候,任务节点会进入等待队列,当线程池中有空闲线程的时候,任务节点将会脱离队头并放到空闲线程中,进而空闲线程变成执行线程。
(3)当任务执行完成后,执行线程转变回空闲线程,且该任务节点会被释放。
2.用户投递任务的过程
(1)当用户投递任务的时候,线程池会首先malloc一个任务节点Task对象,然后对其的回调函数TaskFunc和参数Args进行赋值。
Task *newTask = (Task *) malloc (sizeof (Task));
newTask->TaskFunc= myTask;//比如我的任务是printf(),那么这里就是指向printf()的函数指针myTask
newTask->arg = arg;//这里就是printf()函数的参数
newTask->next = NULL;
(2)使用线程池的互斥锁thread_mutex,锁定任务等待队列,确保当前任务进入等待队列。并且线程池中会查看是否存在空闲线程,且是否达到最大的线程容纳数。
(3)如果有空闲线程,则线程池会发送信号thread_cond给等待队列中正在被阻塞的线程,将其放到空闲线程中。执行线程数cur_task_nums+1。
—— 如果没有空闲线程,且线程数未达到线程池最大容纳量max_task_nums,那么线程池会新建一个空闲线程。然后发信号给等待队列当中被阻塞的任务节点,将其放入空闲线程中。执行线程数+1.
另外
博主自己用C++实现了线程池,代码相对来说简单一些,毕竟stl提供了队列queue,并将线程池与Linux服务器相结合,有兴趣的可以看看,github链接。只要实现了线程池,那么将其应用起来,其实也就是写一个接口的事。