但是由于之前都是在C++类中创建线程。As you see ,在C++类中创建线程是有限制的,为了使已经写好的代码维持最小改动,我将网上广为流传的线程池代码(至今没有找到一个可以顺利运行并直接使用的例子)进行了加工,使其满足我们项目的要求。现在还在测试中,到目前为止还没出现什么问题。遂上传跟大家共享,相互学习,相互交流。
代码的讲解说明暂时还没有写,不确定大家是否需要,而且最近也比较忙,所以就偷了一下懒。
VC6.0中C++实现线程池完整工程加DLL封装下载地址:
http://download.csdn.net/detail/pinghegood/6549975(这个版本忘了封装DLL)
* 2012.12.17修改一个 bug
catch(...){
//m_functionAndParamMutex.Lock(); //可能出现死锁
TRACE0("Process Job error !");
}
*2012.12.19日修改一个bug
BOOL CWorkerThread::Terminate() { //HANDLE tempHandle = GetThreadHandle(); //TRACE0("当前线程Terminate\n"); m_threadRunFlagMutex.Lock(); m_threadRunFlag = FALSE; m_threadRunFlagMutex.Unlock(); ResumeThread(m_ThreadHandle); int ret; ret = ::WaitForSingleObject(m_ThreadHandle,1); if (WAIT_OBJECT_0 != ret) //注意:需要根据返回值来确定是否应该强制结束 { DWORD dwExitCode; GetExitCodeThread(m_ThreadHandle,&dwExitCode); TerminateThread(m_ThreadHandle,dwExitCode); ::CloseHandle(m_ThreadHandle); } m_ThreadHandle = NULL; return TRUE; }
2013.1.5 日:注意提交的任务函数要使用标准调用方式 WINAPI。
2013.4.1 日:修改一个bug,CThreadManage默认构造函数调用失败
删除CThreadManage::CThreadManage(),给带参数构造函数设置默认值。具体原因见http://blog.csdn.net/pinghegood/article/details/8745568
2013.11.14 日:修改资源不释放bug,程序中所以使用CreateMutex创建互斥变量的地方,在其析构函数中要调用ReleaseMutex。使用CreateThread创建线程要注意调用TerminateThread,其实最好不要用CreateThread和TerminateThread,会发生内存泄露,详情请看http://blog.csdn.net/solosure/article/details/6262877。感谢 zybird71 的指正。
一 线程池应用环境:
线程池一般被用于网络服务器程序,这里所提到的服务器程序是指能够接受客户请求并能处理请求的程序,而不仅仅只是指那些接受网络客户请求的网络服务器程序。
多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。但如果对多线程应用不当,会增加对单个任务的处理时间。可以举一个简单的例子:
假设在一台服务器完成一项任务的时间为T
T1 创建线程的时间
T2 在线程中执行任务的时间,包括线程间同步所需时间
T3 线程销毁的时间
显然T = T1+T2+T3。注意这是一个极度简化的假设。
可以看出T1,T3是多线程本身的带来的开销,我们渴望减少T1,T3所用的时间,从而减少T的时间。但一些线程的使用者并没有注意到这一点,所以在程序中频繁的创建或销毁线程,这导致T1和T3在T中占有相当比例。显然这是突出了线程的弱点(T1,T3),而不是优点(并发性)。
线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开销了。
线程池不仅调整T1,T3产生的时间段,而且它还显著减少了创建线程的数目。在看一个例子:
假设一个服务器一天要处理50000个请求,并且每个请求需要一个单独的线程完成。我们比较利用线程池技术和不利于线程池技术的服务器处理这些请求时所产生的线程总数。在线程池中,线程数一般是固定的,所以产生线程总数不会超过线程池中线程的数目或者上限(以下简称线程池尺寸),而如果服务器不利用线程池来处理这些请求则线程总数为50000。一般线程池尺寸是远小于50000。所以利用线程池的服务器程序不会为了创建50000而在处理请求时浪费时间,从而提高效率。
线程池虽然能够减小系统开销,增加系统的稳定性,但是并不是所有使用线程的情况都可以使用线程池。具体说来以下情况下不宜使用线程池:
(1)如果某个线程可能会长时间运行(并因此阻塞其他任务)的任务;
(2)如果需要永久标识来标识和控制线程,比如想使用专用线程来终止该线程;因为线程池具体分配那个线程来执行任务时不能够确定的。理论情况下,对于一个任务每个线程都有机会被分配去执行,但具体是哪一个线程去执行那个任务,就要看当时线程池中线程所处的状态了。
(3)使一个任务具有特定优先级;这个不太不适合使用线程池,因为线程池中对任务的处理都是按照FIFO模式处理的,唯一可以设置的任务优先级就是可以在添加任务时将任务插入到队头而不是队尾。
二 如何使用C++实现自定义的线程池(以下只对主要类中的重要函数进行了说明)
因为在我的项目中,线程池主要用于接收客户端请求并进行处理,在相应处理完成之后将会断开跟客户端的连接的情况,所以符合线程池使用的情况。具体而言,使用的线程池包括以下几个类:
2.1 CThreadManage类:
对外调用接口类,线程池中唯一提供给外界用户操作的接口类。
对应方法:
(1) BOOL AddJob( JOBCALLBACKE callbackfunction , LPVOID lpParam , intParamsize , JobPriority jobPriority) throw (string)
函数功能:AddJob方法,将用户的处理任务提交到线程池的任务队列,以便_DispenseJobThread将其添加到线程池执行。如果在添加任务的时候出现异常,该函数会抛出一个CThreadPoolException类型的异常。
参数说明:
callbackfunction:任务回调函数指针,在该函数中对应着用户自定义的处理逻辑(注意提交的任务函数要使用标准调用方式 WINAPI)。
lpParam :传递给任务回调函数的参数指针。
Paramsize :传递给任务回调函数的参数大小(使用sizeof测量)。
jobPriority :确定该任务的优先级,在这里我们只有两种优先级模式,即 HIGH_PRIORITY和NORMAL_PRIORITY模式。当指定HIGH_PRIORITY时,该任务被添加到队头,指定NORMAL_PRIORITY时则添加到队尾。
(2) void TerminateAll(void)
函数功能:TerminateAll方法,将终止任务分发线程_DispenseJobThread,并调用CThreadPool类的TerminateAll()。最终整个线程池都将被安全的结束掉。
(3) GetJob(JobInfo * lpjob)
函数功能:GetJob方法,从任务队列m_JobList的队头取出一个任务。
2.2 CThreadPool类:
线程池管理类,负责管理线程池中的忙线程队列和闲线程队列,CthreadPool类维护着两个线程队列,他们分别是m_BusyList和 m_IdleList。一开始,预产生的线程对象指针都保存在m_IdleList。当某个线程被分配执行任务时,将其从m_IdleList转移到m_BusyList中。由于多线程同步问题,根据程序的实现方式,我们使用m_AvailNum来统计当前的空闲线程数而不是测量m_IdleList的大小。
对应方法:
(1) CWorkerThread* GetIdleThread(void)
函数功能:从线程池空闲队列中取出一个空闲线程
(2) void AppendToIdleList(CWorkerThread*jobthread)
函数功能:将刚创建的线程添加到线程池的空闲队列中
参数说明:jobthread 是指向线程对象的指针
(3) void MoveToBusyList(CWorkerThread* jobthread)
函数功能:在将任务分配给相应线程之后,将jobthread指向的线程对象放入忙队列中
参数说明:jobthread是指向一个将要执行任务的线程对象的指针
(4) void MoveToIdleList(CWorkerThread *busythread);
函数功能:在线程执行完用户任务之后,将其重新加入空闲线程队列
参数说明:busythread是一个指向完成任务的线程指针
(5) void DeleteIdleThread(intnum);
函数功能:当空闲线程数目超过了m_AvailHigh时,就将结束num个空闲线程,使空闲线程数目保持在m_AvailLow和m_AvailHigh之间
参数说明:num知道要结束的空闲线程数目
(6) void CreateIdleThread(intnum);
函数功能:创建num个空闲线程并将其加入到线程池的空闲队列
参数说明:num是指定要创建的空闲线程数目
2.3 CThread 类:
CThread类是CWorkerThread类的父类,是一个纯虚类,负责线程池中线程的创建以及执行用户调用操作
(1) HANDLE CThread::Start(CThread* cthread)
函数功能:创建一个空闲线程,该线程会调用_ThreadFunction,然后调用CWorkerThread类 的Run()函数,第一次执行Run()函数时,会调用SuspendThread将该线程挂起。直到给该线程分配了任务或者要结束该线程时才会将其重新唤醒。
参数说明:cthread是指向CWorkerThread的线程对象指针
(2) static DWORD WINAPI _ThreadFunction(LPVOID lpParam);
函数功能:线程池中线程回调函数
参数说明:lpParam是指向CWorkerThread的线程对象指针
(3) virtual void Run()=0;
函数功能:线程池中线程的实际执行函数,Run()是纯虚函数在CThread的每个子类中都需要重新实现。
2.4 CWorkerThread 类:
线程池中线程执行函数提供者,负责用户回调函数的调用以及用户函数调用完成之后的清理工作
(1)BOOL CWorkerThread::SetJob(JobInfo tempJobInfo)
函数功能:通过在 CThreadPool的run()函数中调用,将用户回调函数相关信息设置到执行线程。在该线程被唤醒之后将执行设置的回调函数
参数说明:tempJobInfo用户回调函数信息,具体包括回调函数指针,回调函数参数指针以及参数的大小
(2)void CWorkerThread::Run()
函数功能:线程池中线程运行时实际调用的函数,在这个函数中根据SetJob的设置,调用相应的回调函数
(3) BOOL CWorkerThread::Terminate()
函数功能:结束线程,在线程池结束或者该线程需要被结束的时候调用。
三 主要结构体说明
3.1 回调函数信息结构体:
typedefstruct tagJobInfo{ //回调函数指针及参数
JOBCALLBACKE jobcallback;
unsigned char *lpParam;
unsigned int length;
}JobInfo;
3.2 线程优先级机构体
typedefenum tagJobPriority //线程优先级
{
HIGH_PRIORITY,
NORMAL_PRIORITY
}JobPriority;
四 类图
图 1:线程池类图
五 线程池工作流程
图二 :线程池处理流程图
这里只是给出了线程池调用机制的大体流程描述,具体详细流程请结合工程代码仔细研读。
六 如何在已有的C++工程中添加线程池
首先,将头文件拷贝到已经写好的C++工程文件夹下,并将DLL文件拷贝到Debug目录下(如果直接添加cpp文件到工程就不用拷贝DLL文件了)。
然后,在要使用的文件中包含ThreadManage.h头文件,在相应类中定义CThreadManage 对象。
最后,将以前创建线程的代码注释起来,取而代之使用CThreadManage 对象调用AddJob() 函数,回调函数的声明前面加上WINAPI,改变调用方式。在该类的析构函数中调用TerminateAll(void),以便在程序结束时结束线程池。
综上所述,可以在维持原有C++代码最小改动的基础上添加线程池。
七 问题反馈
有问题请联系[email protected]