网站承诺:阿邦网坚持写作客观独立的立场,永远不受金钱影响。秉承为人民生活服务的宗旨,与您分享特邀帮手的经验和知识,帮您解决生活问题,提高生活品质。本文系阿邦网独家稿件,未经许可,任何媒体和个人,不得全部或部分转载,违者必究。
在平时项目中我们经常需要多线程编程,提高系统的CPU利用率,但是如果需要用多线程处理类似轻量级、频率高的任务,因为创建一个线程或者删除一个线程是需要开销的;如果线程数量过大的话,cpu就会浪费很大的精力做线程切换,和创建、删除线程,甚至在系统开销中占很大的一部分,反而会大大降低系统的性能,碰到这种问题怎么办?
自然而然,我们想到了使用线程池——ThreadPool。线程池的原理是首先创建一定量的线程,如果有任务需要线程进行计算,则从线程池中取出线程,调用线程执行该任务,执行完任务,将线程重新置于线程池ThreadPool中。
本文将会就C++实现的一个简单的线程池为Demo,阐述并分析线程池的基本的实现方法,让大家能就ThreadPool能有一个更直观的印象,带大家探索ThreadPool的奥妙。
从上图(线程池流程图)中我们可以看到线程池ThreadPool主要分为以下三个类
WorkItem: 任务对象,标识需要实现的具体的工作
WorkItemQueue: 任务对象队列,当用户向ThreadPool提交任务时,必须先将该任务WorkItem压入任务对象队列
ThreadPool:负责创建,分配线程对象
线程池流程图具体如下:
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状态
本章将给出线程池的时序图,阐述线程池工作的如何具体工作的。
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中两个重要的信号量,及其所含意义:
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): 线程获取任务函数
自本章起,我们将重点介绍线程池ThreadPool的几个重要成员函数及其实现。
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执行
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;
}
下面WorkLoop相应的伪码:
void ThreadPool::WorkLoop()
{
WorkItem *workItem;
while (true)
{
GetWorkItem(workItem);
workItem->Work();
}
}
ThreadPool::AddWorkItem(ThreadWorkItem *workItem)
用户提交任务函数,功能是向任务队列WorkItemQueue中加入任务
下面AddWorkItem相应的伪码:
{
WaitSemaphore(&AvailableQueueSlots); //等待任务队列是否满
ScopedLock lockQueue(mWorkItemQueueMutex); //对任务队列加互斥锁
WorkItemQueue.push(workItem); //向任务队列中加入任务
sem_post(&AvailableWorkItems); //标识任务队列中有新任务,唤醒线程池中的等待线程,来执行这个任务
}
WorkerLoop():线程主函数
4、当线程池中Running线程执行完对应任务workitem,则该线程状态转为Waiting,继续等待任务队列中是否有任务——sem_wait(availableWorkItem)
阅读(2010) | 评论(0) | 转发(0) |