概述
线程池主要有两种实现模型:
半同步/半异步模型: 一个线程(ManagerThread)在工作队列上侦听,一旦主线程往工作队列push新的工作任务,ManagerThread拿出这个任务分派给某个空闲的工作线程去执行。在这个过程中ManagerThread负责任务调度,真正做事的是WorkerThread。异步指对任务请求是异步的,同步指分派处理任务过程中需要做同步操作。
领导者/跟随者模型:有一个线程起初是leader,其他的线程是followers,当有任务请求到达时,leader接下这个任务处理,同时从followers中选择一个新的leader ,这个过程中始终只有一个leader线程在侦听任务。
两者各有优缺点,这里选择半同步/半异步模型来实现这个线程池。
实现
A. Manager Thread
半同步/半异步模型的核心是ManagerThread,ManagerThread工作流程图是:
在这个过程中需要注意的是两个队列:
1.工作任务队列,数据结构定义为:
typedef struct _task_list
{
task_node *head;
task_node *tail;
pthread_mutex_t *mutex;
pthread_cond_t *cond;
}task_list;
主线程即调用线程往任务队列末尾添加新任务,ManagerThread从队首拿任务去派发,这个过程遵守FIFO原则。因为不同线程操作这个队列所以需要同步。
2.空闲工作线程队列,数据结构定义为:
typedef struct _idle_thread_id_array
{
int *idxs;
int size;
pthread_mutex_t *mutex;
pthread_cond_t *cond;
}idle_thread_array;
这个队列主要是在Manager Thread和Worker Threads,Worker Threads互相之间同步,Worker Threads做完了Manager Thread派发的任务后把自己置入idle array中,Manager Thread每次从idle array中取一个idle Worker Thread去做任务,如果idle array中没有空闲线程,且池子里的线程数没有超过池子的大小,就会去新建一个Worker Thread,如果超过了,Manager Thread就会在idle array上等待,直到有新的Worker Thread变为空闲。
空闲工作线程队列数据结构中,其实是一个int数组,保存threads数组的索引,threads数组是一个所有Worker Threads构成的数组,数据结构是
typedef struct _thread_array
{
thread_info *threads;
int size;
int *unused;
int unused_size;
}thread_array;
thread_info是线程信息结构体,thread_array包括thread_info数组,和一个"unused int"数组,这个数组是因为线程池有自动回收策略,当一个WorkerThread空闲了一定时间而没有被分派新任务时,会被回收以节省系统资源,相当于从数组中删去某一项,这时会在数组中出现很多‘洞’,unused保存这些‘洞’索引,当新 的Worker Thread被添加进池子时,从unused选一个位置去放置新线程的信息。
B. Worker Threader
WorkerThread的工作流程图是:
具体细节实现主要是注意尽量减小同步代码的颗粒度。
测试
测试代码可以这样写,让线程池做大量任务,任务执行过程是将该任务相关变量置1(初始化时是0),程序退出时,检查任务是否都完成,打印完成任务所花的时间。作为比对,同时写一个单线程测试案例完成上述过程,最后比较两者所花的时间。
我的机器是双核四线程,在没有其他很占cpu的进程开启下,使用线程池跑的案例所耗费的时间是没使用的1/4, 且所有任务都成功完成。这也验证了我这台机器可以同时运行四个线程。
转载于:https://www.cnblogs.com/persistentsnail/archive/2012/05/14/3294858.html