Linux C++ 线程池

来源:互联网 发布:易观千帆 数据来源 编辑:程序博客网 时间:2024/04/28 17:10
在平时项目中我们经常需要多线程编程,提高系统的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状态 
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类图[点击放大]
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; }



0 0