这篇文章是我在看了某平台的录播课进行的一个比较简单的笔记,虽然是公开课,但是也从中学到了不少新的知识,还是有所收获的,故此记录一下。
一、原理
1.1 解决的是什么问题
平台主推的课程是Linux C/C++后端服务器开发的,提出了一个并发问题,我简单的描述一下:服务器通过开线程进行处理请求,那么能够同时开1w个线程吗?
主讲老师给出的答案是不能:假设开一个线程系统要分8M空间保存堆栈的数据,那么开1w个线程,大概需要80个G,显然这是不合理的。于是引出了线程池的概念,线程池本质上实现的是一个缓冲区的功能。
来自百度百科对线程池概念的描述:
线程过多会带来调度开销,进而影响缓存局部性和整体性能;而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。
1.2 组成部分
线程池由三个部分组成,下面这个是来自百度百科的解释。而主讲老师总结的三个部分是:管理器、工作线程、任务线程,并且也给出了一个非常生动的类比实例。
1、线程池管理器(ThreadPoolManager):用于创建并管理线程池
2、工作线程(WorkThread): 线程池中线程
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行。
与线程池处理过程非常相似的一个生活中的例子就是银行柜台办理业务的流程。柜员是工作线程,处理工作业务;客户是任务线程,排队等候处理;而银行的排队系统就是管理器,负责什么时候柜员处理客户的业务,什么时候轮到客户处理业务(即排队)。
二、实现
线程池的操作涉及到线程的互斥锁,因为每个工作线程在处理业务的时候,都要通过访问线程池获得任务线程的信息,所以需要加上互斥锁,防止不同的工作线程去处理同一个任务线程。
2.1 数据结构
下面三个数据结构分别对应的是工作线程、任务线程、线程池管理,都带有指向前后节点的结构体指针,用来实现队列的插入和删除操作。
typedef struct NWORKER {
pthread_t thread;
int terminate;
struct NWORKQUEUE *workqueue;
struct NWORKER *prev;
struct NWORKER *next;
} nWorker;
typedef struct NJOB {
void (*job_function)(struct NJOB *job);
void *user_data;
struct NJOB *prev;
struct NJOB *next;
} nJob;
typedef struct NWORKQUEUE {
struct NWORKER *workers;
struct NJOB *waiting_jobs;
pthread_mutex_t jobs_mtx;
pthread_cond_t jobs_cond;
} nWorkQueue;
2.2 队列操作
线程池就是使用队列的方法实现的,百度百科关于队列的解释为:
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
代码中使用非常巧妙的方法,因为队列的操作都是只在表的前端进行删除,后端进行插入,可以抽象成一个统一的接口进行处理,并且用宏定义的方式实现:
#define LL_ADD(item, list) do { \
item->prev = NULL; \
item->next = list; \
list = item; \
} while(0)
#define LL_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)
2.3 业务处理
1. 处理工作线程
函数功能:对应的工作线程处理对应的任务线程。
ptr:线程池。
static void *ntyWorkerThread(void *ptr);
2. 创建线程池
函数功能:创建线程池,并初始化工作线程。
workqueue:存放工作线程的队列;numWorkers:工作线程的数量。
int ntyThreadPoolCreate(nThreadPool *workqueue, int numWorkers);
3. 关闭线程池
函数功能:将线程池中的任务线程和工作线程清空。
void ntyThreadPoolShutdown(nThreadPool *workqueue);
5. 线程池队列
函数功能:将任务线程放到对应需要处理的工作线程当中。
workqueue:线程池队列(这里的命名不是很好);job:任务线程
void ntyThreadPoolQueue(nThreadPool *workqueue, nJob *job);
6. 任务线程的回调函数
函数功能:当任务线程完成时,调用的函数。
void king_counter(nJob *job);
三、 小结
由于能力有限,难免会有错漏,望不吝赐教。特地找到了源码放到了我的GitHub:https://github.com/pzibang/threadpool.git