线程池流程图具体如下:
1、创建线程池ThreadPool, ThreadPool负责创建n个线程,每个线程都处于Waiting状态。
2、用户调用AddWorkItem(workitem),sem_wait(availableQueueSlots)——如果任务队列WorkItemQueue已满,则等待信号量availableQueueSlots; 如果WorkItemQueue未满,则sem_post(availableWorkItem), 对信号量availableWorkItem加一,最后向WorkItemQueue中加入任务workitem.
3、线程池中的Waiting线程sem_wait(availableWorkItem)——如果任务队列中没有任务,则线程继续处于等待availableWorkItem;如果任务队列中存在任务,则某等待线程从任务对象队列WorkItemQueue中取出workitem, 执行该线程,并sem_post(availabelQueueSlots)——因为从队列中已经取走该workitem,此时线程处于Running状态
4、当线程池中Running线程执行完对应任务workitem,则该线程状态转为Waiting,继续等待任务队列中是否有任务——sem_wait(availableWorkItem)
本章将给出线程池的时序图,阐述线程池工作的如何具体工作的。
线程池时序图 来源:朱翔
ThreadPool Sequence Diagram(见上图):上图重点描述了用户线程(User Thread),如何通过线程池(ThreadPool)获得所需线程,用于处理用户提交的任务。 具体的User Story如下:
1、首先用户线程(User Thread)调用ThreadPool::Open(2,10)打开线程池,
2、标识线程池中线程的个数,10标识任务队列的大小。Open函数就会调用CreateThread来分别创建线程A、线程B。当线程A、B创建完毕,两个线程就进入WorkerLoop——即进入等待任务,获得任务,执行任务,继续等待任务…,循环往复的过程,直到线程池close该线程。
3、线程进入WorkerLoop之后,就进入等待获取任务阶段,即GetWorkItem,通过等待sem_wait(availableworkItem),availableworkItem信号量标识任务队列中是否有任务。
4、用户线程向任务队列WorkItemQueue中添加任务,即AddWorkItem(workItemA),然后sem_pos(availableworkItem),将availableworkItem加一,标识任务队列中有任务,来唤醒线程池中处于等待状态的任务——即处于wait_sem(availableworkItem)状态的线程,如图中所示,信号量availableworkItem唤醒线程A,线程A即调用任务workItemA->Work(),执行相关的任务。执行完毕workItemA->Work(),ThreadA又调用GetWorkItem,然后处于sem_wait(availableworkItem)。
5、之后,用户线程又向任务队列WorkItemQueue中添加任务B,然后sem_pos(availableworkItem),将availableworkItem加一,标识任务队列中有任务,来唤醒线程池中处于等待状态的任务——即处于wait_sem(availableworkItem)状态的线程,如图中所示,信号量availableworkItem唤醒线程B,线程B即调用任务workItemB->Work(),执行相关的任务。执行完毕workItemB->Work(),ThreadB又调用GetWorkItem,然后处于sem_wait(availableworkItem)。
上述重点描述了线程池的构成,和工作流程,自本章起,将会具体介绍ThreadPool中的模块,在介绍具体模块之前,首先介绍一下ThreadPool中两个重要的信号量,及其所含意义:
ThreadPool类图
sem_t mavailableQueueSlots; 因为任务队列WorkItemQueue是有大小限制的,信号量AvailableQueueSlots标识任务队列是否已满,如果已满,用户线程如果调用AddWorkItem(workItem),向队列提交任务,将处于阻塞状态,直到线程池中的线程提取了任务队列中的任务,才会唤醒AddWorkItem(workItem)继续执行。
sem_t mavailableWorkItems; 信号量availableWorkItems标识任务队列是否有任务,如果任务队列中没有任务,线程池中的等待线程将会因为sem_wait(availableWorkItem)而进入休眠等待状态,直到用户向任务队列提交任务,信号量availableWorkItems将会唤醒ThreadPool中相应个数的等待线程来执行相应的workItem(任务)。
从ThreadPool类图(见上图)中我们可以看到ThreadPool类主要包含以下几个重要成员函数: Open(int maxThreadNumber, int maxQueueSize):线程池打开函数 Close():线程池关闭函数 AddWorkItem(WorkItem *workItem):用户提交任务函数 GetWorkItem(WorkItem *workItem): 线程获取任务函数 WorkerLoop():线程主函数
ThreadPool::Open(int maxThreadNumber, int maxQueueSize) 打开线程池,对线程池进行初始化,在线程池中创建maxThreadNumber个线程;并限制任务队列WorkItemQueue的最大个数为maxQueueSize,即sem_init(&AvailableQueueSlots, 0, maxQueueSize);并使用CreateThread来创建线程函数。 ThreadPool::WorkLoop线程主函数,线程池中创建的每个线程执行WorkLoop,对于每个线程,它的执行流程如下:
1、GetWorkItem(workItem),如果当前在任务队列中存在任务,则GetWorkItem返回,并带回相应的任务项——workItem,继续执行步骤2
2、调用workItem->Work(),执行完返回步骤1执行 下面WorkLoop相应的伪码:
void ThreadPool::WorkLoop()
void ThreadPool::WorkLoop()
{
WorkItem *workItem;
while (true)
{
GetWorkItem(workItem);
workItem->Work();
}
}
ThreadPool::AddWorkItem(ThreadWorkItem *workItem) 用户提交任务函数,功能是向任务队列WorkItemQueue中加入任务 下面AddWorkItem相应的伪码:
ThreadPool::AddWorkItem(ThreadWorkItem *workItem)
{
WaitSemaphore(&AvailableQueueSlots); //等待任务队列是否满
ScopedLock lockQueue(mWorkItemQueueMutex); //对任务队列加互斥锁
WorkItemQueue.push(workItem); //向任务队列中加入任务
sem_post(&AvailableWorkItems); //标识任务队列中有新任务,唤醒线程池中的等待线程,来执行这个任务
}
GetWorkItem(WorkItem *workItem) 线程获取任务函数,线程池中的等待线程等待任务队列中出现任务,即等待信号量AvailableWorkItems,如果有任务,则首先对任务队列加范围锁(ScopedLock),当从任务队列中取出任务之后,对信号量AvailableQueueSlots加一, 下面GetWorkItem相应的伪码:
int ThreadPool::GetWorkItem(ThreadWorkItemPtr& workItem)
{
WaitSemaphore(&AvailableWorkItems); //等待任务队列中出现任务
ScopedLock lock(WorkItemQueueMutex);//对任务队列加互斥锁
workItem = WorkItemQueue.front();//从任务队列中拿出一个任务
mWorkItemQueue.pop();
sem_post(&AvailableQueueSlots);//由于已被取走一个任务,故对信号量AvailableQueueSlots加一
return 0;
}