Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1269121
  • 博文数量: 389
  • 博客积分: 2874
  • 博客等级: 少校
  • 技术积分: 3577
  • 用 户 组: 普通用户
  • 注册时间: 2009-10-24 10:34
文章分类

全部博文(389)

文章存档

2020年(2)

2018年(39)

2017年(27)

2016年(3)

2015年(55)

2014年(92)

2013年(54)

2012年(53)

2011年(64)

分类: C/C++

2011-09-06 11:36:27

ThreadPool分析
作者:帮手朱翔
问帮手给我写信rss 邮件订阅

网站承诺:阿邦网坚持写作客观独立的立场,永远不受金钱影响。秉承为人民生活服务的宗旨,与您分享特邀帮手的经验和知识,帮您解决生活问题,提高生活品质。本文系阿邦网独家稿件,未经许可,任何媒体和个人,不得全部或部分转载,违者必究。
线程池流程图 来源:朱翔
[]
在平时项目中我们经常需要多线程编程,提高系统的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中的模块,在介绍具体模块之前,首先介绍一下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) |
给主人留下些什么吧!~~