文章目录
一、线程池介绍
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。 ——百度百科
简单来说,当需要多个线程时,如有百万个客户端,不可能同时开百万个线程并发运行。此时线程池可以满足需求,在适当时间对线程创建销毁。(注:高并发还涉及到IO多路复用哈,以后再写(^ - ^))
线程池作用:
1、避免线程太多,使得内存耗尽
2、避免创建和销毁线程的代价
3、任务与执行分离
举个栗子:
对于日志文件,执行写日志文件这个任务(落盘)可以丢给线程池,实现 写文件任务 与 执行此任务分离。
线程池形象化理解
以银行办理业务场景为例,每一个来办理业务的人是一个任务,每一个柜员相当于执行。多个任务组成一个任务队列,多个柜员组成一个执行队列。银行有公示牌,让一个办业务的人对应一个柜员,使银行有序工作。公示牌对应线程池的管理组件。管理组件就是锁。
三个要素:任务队列 执行队列 管理组件
二、代码实现
代码放在github 欢迎指正https://github.com/ZSFsmile/threadpool/blob/master/threadpool.c
三、代码解析
1、任务队列
双向链表,用结构体 struct nTask 实现
struct nTask{
void (*task_func)(void *arg); //任务的功能
void *user_data; //任务相应的参数
struct nTask* prev;
struct nTask* next;
};
2、执行队列
双向链表,用结构体 struct nWorker 实现
struct nWorker{
pthread_t threadid; //线程id
struct nManager *manager; //管理组件
int terminate; //线程函数执行任务循环的退出的标志
struct nWorker* prev;
struct nWorker* next;
};
3、管理组件
用结构体 struct nManager 实现
管理组件就可以看作我们常说的线程池
typedef struct nManager{
struct nTask* tasks; //任务队列
struct nWorker* workers; //执行队列
pthread_mutex_t mutex; //互斥锁
pthread_cond_t cond; //条件变量
}ThreadPool;
4、链表的操作
插入与删除操作,依旧用宏函数实现
#define LIST_INSERT(item, list) do { \
item->prev = NULL; \
item->next = list; \
if ((list) != NULL) (list)->prev = item; \
(list) = item; \
} while(0)
#define LIST_REMOVE(item, list) do { \
if (item->prev != NULL) item->prev->next = item->next; \
if (item->next != NULL) item->next->prev = item->prev; \
if (list == item) list = item->next; \
item->prev = item->next = NULL; \
} while(0)
5、线程池Create API
严格说应该是init
int nThreadPoolCreate(ThreadPool* pool,int numWorkers);
第一个参数pool 需要初始化的线程池
第二个参数numWorkers 任务的数量,需要创建numWorkers 个任务并添加到pool中
6、线程池Destory API
int nThreadPoolCreate(ThreadPool* pool,int numWorkers);
销毁线程池里的所有任务,方法是
将每个任务的terminate置1并且唤醒所有阻塞在条件变量pool->cond上的全部线程
7、线程池任务push API
int nThreadPoolPushTask(ThreadPool* pool,struct nTask* task);
第一个参数线程池pool
第二个参数添加到线程池里的任务
添加任务过程中需要唤醒一个因为任务队列为空而被阻塞在条件变量pool->cond上的线程
8、线程回调函数
static void *nThreadPoolCallback(void *arg);
一定要注意回调函数不要是要执行的任务,而是每一个执行,相当于营业厅里面的柜员,多个线程回调函数(柜员)将线程池里的任务全部并发执行。
并发执行当然少不了锁 ^ o ^
四、结语
线程池代码还算复杂,大家学习时最好先理清函数的功能再去看函数的实现。