热门关键字:  ubuntu  分区  Fedora  linux系统进程  函数

当前位置 :| 主页>Linux教程>内核研究>

Linux下通用线程池的创建与使用(下)

来源: 作者: 时间:2007-04-07 Tag: 点击:

在CThreadPool中存在两个链表,一个是空闲链表,一个是忙碌链表。Idle链表中存放所有的空闲进程,当线程执行任务时候,其状态变为忙碌状态,同时从空闲链表中删除,并移至忙碌链表中。在CThreadPool的构造函数中,我们将执行下面的代码:

for(int i=0;i<m_InitNum;i++) 

{ 

CWorkerThread* thr = new CWorkerThread(); 

AppendToIdleList(thr); 

thr->SetThreadPool(this); 

thr->Start(); //begin the thread,the thread wait for job 

}

在该代码中,我们将创建m_InitNum个线程,创建之后即调用AppendToIdleList放入Idle链表中,由于目前没有任务分发给这些线程,因此线程执行Start后将自己挂起。

事实上,线程池中容纳的线程数目并不是一成不变的,其会根据执行负载进行自动伸缩。为此在CThreadPool中设定四个变量:

m_InitNum:处世创建时线程池中的线程的个数。

m_MaxNum:当前线程池中所允许并发存在的线程的最大数目。

m_AvailLow:当前线程池中所允许存在的空闲线程的最小数目,如果空闲数目低于该值,表明负载可能过重,此时有必要增加空闲线程池的数目。实现中我们总是将线程调整为m_InitNum个。

m_AvailHigh:当前线程池中所允许的空闲的线程的最大数目,如果空闲数目高于该值,表明当前负载可能较轻,此时将删除多余的空闲线程,删除后调整数也为m_InitNum个。

m_AvailNum:目前线程池中实际存在的线程的个数,其值介于 m_AvailHigh和m_AvailLow之间。如果线程的个数始终维持在m_AvailLow和m_AvailHigh之间,则线程既不需要创建,也不需要删除,保持平衡状态。因此如何设定m_AvailLow和m_AvailHigh的值,使得线程池最大可能的保持平衡态,是线程池设计必须考虑的问题。

线程池在接受到新的任务之后,线程池首先要检查是否有足够的空闲池可用。检查分为三个步骤:

(1)检查当前处于忙碌状态的线程是否达到了设定的最大值m_MaxNum,如果达到了,表明目前没有空闲线程可用,而且也不能创建新的线程,因此必须等待直到有线程执行完毕返回到空闲队列中。

(2)如果当前的空闲线程数目小于我们设定的最小的空闲数目m_AvailLow,则我们必须创建新的线程,默认情况下,创建后的线程数目应该为m_InitNum,因此创建的线程数目应该为( 当前空闲线程数与m_InitNum);但是有一种特殊情况必须考虑,就是现有的线程总数加上创建后的线程数可能超过m_MaxNum,因此我们必须对线程的创建区别对待。

if(GetAllNum()+m_InitNum-m_IdleList.size() < m_MaxNum ) 

CreateIdleThread(m_InitNum-m_IdleList.size()); 

else 

CreateIdleThread(m_MaxNum-GetAllNum());

如果创建后总数不超过m_MaxNum,则创建后的线程为m_InitNum;如果超过了,则只创建( m_MaxNum-当前线程总数 )个。

(3)调用GetIdleThread方法查找空闲线程。如果当前没有空闲线程,则挂起;否则将任务指派给该线程,同时将其移入忙碌队列。

当线程执行完毕后,其会调用MoveToIdleList方法移入空闲链表中,其中还调用m_IdleCond.Signal()方法,唤醒GetIdleThread()中可能阻塞的线程。

CWorkerThread

CWorkerThread是CThread的派生类,是事实上的工作线程。在 CThreadPool的构造函数中,我们创建了一定数量的CWorkerThread。一旦这些线程创建完毕,我们将调用Start()启动该线程。 Start方法最终会调用Run方法。Run方法是个无限循环的过程。在没有接受到实际的任务的时候,m_Job为NULL,此时线程将调用Wait方法进行等待,从而处于挂起状态。一旦线程池将具体的任务分发给该线程,其将被唤醒,从而通知线程从挂起的地方继续执行。CWorkerThread的完整定义如下:

class CWorkerThread:public CThread 

{ 

private: 

CThreadPool* m_ThreadPool; 

CJob* m_Job; 

void* m_JobData; 

CThreadMutex m_VarMutex; 

bool m_IsEnd; 

protected: 

public: 

CCondition m_JobCond; 

CThreadMutex m_WorkMutex; 

CWorkerThread(); 

virtual ~CWorkerThread(); 

void Run(); 

void SetJob(CJob* job,void* jobdata); 

CJob* GetJob(void){return m_Job;} 

void SetThreadPool(CThreadPool* thrpool); 

CThreadPool* GetThreadPool(void){return m_ThreadPool;} 

}; 

CWorkerThread::CWorkerThread() 

{ 

m_Job = NULL; 

m_JobData = NULL; 

m_ThreadPool = NULL; 

m_IsEnd = false; 

} 

CWorkerThread::~CWorkerThread() 

{ 

if(NULL != m_Job) 

delete m_Job; 

if(m_ThreadPool != NULL) 

delete m_ThreadPool; 

} 

void CWorkerThread::Run() 

{ 

SetThreadState(THREAD_RUNNING); 

for(;;) 

{ 

while(m_Job == NULL) 

m_JobCond.Wait(); 
m_Job->Run(m_JobData); 

m_Job->SetWorkThread(NULL); 

m_Job = NULL; 

m_ThreadPool->MoveToIdleList(this); 

if(m_ThreadPool->m_IdleList.size() > m_ThreadPool->GetAvailHighNum()) 

{ 

m_ThreadPool->DeleteIdleThread(m_ThreadPool->m_IdleList.size()-m_T 

hreadPool->GetInitNum()); 

} 

m_WorkMutex.Unlock(); 

} 

} 

void CWorkerThread::SetJob(CJob* job,void* jobdata) 

{ 

m_VarMutex.Lock(); 

m_Job = job; 

m_JobData = jobdata; 

job->SetWorkThread(this); 

m_VarMutex.Unlock(); 

m_JobCond.Signal(); 

} 

void CWorkerThread::SetThreadPool(CThreadPool* thrpool) 

{ 

m_VarMutex.Lock(); 

m_ThreadPool = thrpool; 

m_VarMutex.Unlock(); 

}

当线程执行任务之前首先必须判断空闲线程的数目是否低于m_AvailLow,如果低于,则必须创建足够的空闲线程,使其数目达到m_InitNum个,然后将调用MoveToBusyList()移出空闲队列,移入忙碌队列。当任务执行完毕后,其又调用MoveToIdleList()移出忙碌队列,移入空闲队列,等待新的任务。

除了Run方法之外,CWorkerThread中另外一个重要的方法就是 SetJob,该方法将实际的任务赋值给线程。当没有任何执行任务即m_Job为NULL的时候,线程将调用m_JobCond.Wait进行等待。一旦 Job被赋值给线程,其将调用m_JobCond.Signal方法唤醒该线程。由于m_JobCond属于线程内部的变量,每个线程都维持一个 m_JobCond,只有得到任务的线程才被唤醒,没有得到任务的将继续等待。无论一个线程何时被唤醒,其都将从等待的地方继续执行m_Job-> Run(m_JobData),这是线程执行实际任务的地方。

在线程执行给定Job期间,我们必须防止另外一个Job又赋给该线程,因此在赋值之前,通过m_VarMutex进行锁定, Job执行期间,其于的Job将不能关联到该线程;任务执行完毕,我们调用m_VarMutex.Unlock()进行解锁,此时,线程又可以接受新的执行任务。

在线程执行任务结束后返回空闲队列前,我们还需要判断当前空闲队列中的线程是否高于m_AvailHigh个。如果超过m_AvailHigh,则必须从其中删除(m_ThreadPool->m_IdleList.size ()-m_ThreadPool->GetInitNum())个线程,使线程数目保持在m_InitNum个。



相关文章:
精通initramfs构建step by step
Linux利用kexec迅速切换内核
进程上下文VS中断上下文
内核通知链 学习笔记
linux spi子系统驱动分析
menuconfig 配置选项
《Linux操作系统内核实习》之练习一
udev详解
什么叫微内核,宏内核?
Linux 信号signal处理机制
开发简单的 Linux2.6 内核模块
删除内核的perl脚本
Linux2.6内核usb gadget驱动移植
GCC hacks in the Linux kernel
iomem
kernel学习的想法
让自己的驱动支持udev
linux内核编译步骤
内核的等待队列
Linux内核wait_queue深入分析
升级和删除内核
SD卡驱动分析2
Linux Kernel VDSO本地权限提升漏洞
内核中的TCP的追踪分析-15-TCP(IPV4)的客户端与
linux 2.6内核可加载模块的编译
内核模块HelloWorld
在环回接口上发送一个数据报
ARP初始化
1分钟编译FreeBSD内核
linux设备模型之uart驱动架构分析