一、设计线程池的优点、作用
- 线程使应用能够更加充分合理的协调利用cpu 、内存、网络、i/o等系统资源
- 线程的创建需要开辟虚拟机栈,本地方法栈、程序计数器等线程私有的内存空间
- 在线程的销毁时需要回收这些系统资源。频繁的创建和销毁线程会浪费大量的系统资源,增加并发编程的风险
- 另外,在服务器负载过大的时候,如何让新的线程等待或者友好的拒绝服务?这些丢失线程自身无法解决的。所以需要通过线程池协调多个线程,并实现类似主次线程隔离、定时执行、周期执行等任务
- 作用:
- 利用线程池管理并复用线程、控制最大并发数等
- 实现任务线程队列缓存策略和拒绝机制
- 实现某些与时间相关的功能,如定时执行、周期执行等
- 隔离线程环境。比如,交易服务和搜索服务在同一台服务器上,分别开启两个线程池,交易线程的资源消耗明显要大;因此,通过配置独立的线程池,将较慢的交易服务与搜索服务隔开,避免个服务线程互相影响
二、线程池的设计
- 线程池设计中包含5个内容(3线程2池):
- 任务管理线程
- 管理线程
- 监控线程
- 任务池
- 线程池
- 这5者都是由主线程创建的
任务管理线程:
- 功能:服务端在主线程中进行客户端的监听和接收(accept),当有一个客户端连接进来之后,任务管理线程会将该客户端封装为一个任务节点,并将任务节点放置到任务池中
- 特点:管理线程会阻塞(pthread_cond_wait)在任务池的条件变量上,当接收到一个客户端连接之后,任务管理线程将其封装为一个任务节点,然后放入任务池中,在放入任务池之后,会pthread_cond_signal通知管理线程,然后此时管理线程就开始工作
管理线程:
- 功能:如果没有任务,管理线程会阻塞(pthread_cond_wait)在任务池的条件变量上,当任务管理线程将任务添加进任务池之后会激活(pthread_cond_signal)该条件变量,从而管理线程被激活,开始工作
- 激活之后做以下事情:
- 从任务池中取出一个任务,然后将任务池的状态更新
- 从线程池中取出一个空闲线程,然后将线程池的状态更新
- 将取出来的任务和线程池进行关联
- pthread_cond_signal通知取出来的线程开始工作
监控线程:
- 功能:这个线程主要来对线程池进行侦查,分析线程池的状态然后做出相应的改变等
- 例如:
- 如果线程池中的很多线程长时间的处于空闲状态,那么就销毁一些线程,从而降低系统的消耗
- 如果线程池中的忙碌线程数量达到一定的数量,线程将要不够用了,就创建一些新的线程加入到线程池中
- 等等....
- 通常在线程池中会设有一些Flag(标志),监控线程会来定期检查这个标志,从而做出相应的动作(自己设计,没有固定的要求)
任务池:
- 在编程中可以设计为一个与任务节点相同类型的单链表,用来存储每个任务节点
- 为什么设计为单链表:因为任务完成和添加只需要再尾部删除或添加就可以了,不需要遍历链表
- 特点:主进程在对任务池初始化时,任务池是空的(链表为空),只有一个头指针,因为此时还没有任务传进来
- 条件变量:任务池有一个条件变量,管理线程会阻塞在这个条件变量上。当任务管理线程将任务添加进任务池之后会随即激活这个条件变量,那么管理线程就可以从任务池中取任务了
//线程池(双链表) typedef struct pthread_queue { int number; /* 线程池中的线程数量*/ struct pthread_node *head; /* 线程池的头指针*/ struct pthread_node *rear; /* 线程池的尾指针*/ pthread_cond_t cond; /* 线程池的条件变量*/ pthread_mutex_t mutex; /* 锁,也是为了实现互斥与高并发*/ }PTHREAD_QUEUE_T;//线程节点,存在于线程池链表中 typedef struct pthread_node { pthread_t tid; /* 线程号*/ int flag; /* 当前线程的标志 1:busy, 0:free. */ struct task_node *work; /* 如果线程正在执行任务,这个代表执行的哪一个任务*/ struct pthread_node *next; /* 在线程池中的下一个线程节点*/ struct pthread_node *prev; /* 在线程池中的前一个线程节点*/ pthread_cond_t cond; /* 条件变量,有任务来时被激活*/ pthread_mutex_t mutex; /* 锁*/ } THREAD_NODE;
线程池:
- 在编程中可以设计为一个与线程节点相同类型的双链表,用来存储每个线程节点
- 为什么设计为双链表:因为线程池可以需要进行遍历,来判断线程池中线程的状态
- 特点:主进程在对线程池初始化时,线程池是指定数量了的(链表中有指定数量的节点),因为要一次性的初始化指定数量的线程,为了防止后面再创建或销毁线程而带来的效率降低
- 线程的条件变量:主进程在对线程池初始化,线程池中的每个线程都会有一个条件变量,并且每个线程都阻塞在自己的条件变量上,当管理线程在处理任务时,将线程从线程池中取出来,就会将这个这个线程的条件变量激活
- 线程池的条件变量:线程池也有一个条件变量,当线程池中的线程数量为0时(表示没有空闲线程可以用了),那么当管理线程去线程池中取任务时,发现没有空闲线程可以用,就会阻塞在这个条件变量上。在每个线程的执行函数中,如果这个线程的任务完成了,那么就会pthread_cond_signal激活线程池的条件变量,来告诉自己完成任务了,此时线程池中有空闲线程可以使用了
//任务池(单链表)
typedef struct task_queue
{
pthread_mutex_t mutex; /* 任务池的锁*/
pthread_cond_t cond; /* 任务池的条件变量*/
struct task_node *head; /* 任务池的头指针*/
int number; /* 当前任务池中的任务数量(包括被处理的和未处理的) */
} TASK_QUEUE_T;//任务节点
typedef struct task_node
{
void *arg; /* 任务执行函数需要的参数 */
void *(*fun) (void *); /* 任务执行函数指针*/
pthread_t tid; /* 执行该任务的线程ID*/
int work_id; /* 自己的work ID*/
int flag; /* 标志 1: assigned(该任务被处理了), 0: unassigned. (该任务没有被处理)*/
struct task_node *next; /* 因为任务池是单链表,这个指针指向后一个任务节点 */
pthread_mutex_t mutex; /* 锁*/
} TASK_NODE;
- 任务和线程时如何进行匹配绑定的?任务节点中有个线程ID,线程节点中有个任务结构体对象。实现你中有我,我中有你,从而达到匹配
三、源码地址
没积分下载,文章下面留邮箱,12小时给你发过去运行:
- 源码下载之后输入ifconfig查看自己虚拟机的网卡名称,pthread_pool.c源码中服务器绑定的IP地址为自己网卡的IP,例如此处我们的网卡名为ens33,如果你的网卡名称是“eth0”等,请更改为自己网卡的名称
- 进入pthread_pool1目录后输入make编译,然后运行pthread_pool1_miain程序,服务器默认端口为8888
四、结束语
- 本文介绍的线程池实现的优点复杂,一个简略版本的线程池参阅