VC实现线程池

来源:互联网 发布:福州淘宝托管 编辑:程序博客网 时间:2024/05/17 08:56

 这两天在做关于网络蜘蛛的程序,希望可以通过线程池来提高程序的性能,网上搜索了一下,看到这方面的东西还不少,跟大家分享一下!~有许多应用程序创建的线程花费了大量时间在睡眠状态来等待事件的发生。还有一些线程进入睡眠状态后定期被唤醒以轮询工作方式来改变或者更新状态信息。线程池可以让你更有效地使用线程,它为你的应用程序提供一个由系统管理的工作者线程池。至少会有一个线程来监听放到线程池的所有等待操作,当等待操作完成后,线程池中将会有一个工作者线程来执行相应的回调函数。
  你也可以把没有等待操作的工作项目放到线程池中,用QueueUserWorkItem函数来完成这个工作,把要执行的工作项目函数通过一个参数传递给线程池。工作项目被放到线程池中后,就不能再取消了。
  Timer-queue timers和Registered wait operations也使用线程池来实现。他们的回调函数也放在线程池中。你也可以用BindIOCompletionCallback函数来投递一个异步IO操作,在IO完成端口上,回调函数也是由线程池线程来执行。
  当第一次调用QueueUserWorkItem函数或者BindIOCompletionCallback函数的时候,线程池被自动创建,或者Timer-queue timers或者Registered wait operations放入回调函数的时候,线程池也可以被创建。线程池可以创建的线程数量不限,仅受限于可用的内存,每一个线程使用默认的初始堆栈大小,运行在默认的优先级上。
  线程池中有两种类型的线程:IO线程和非IO线程。IO线程等待在可告警状态,工作项目作为APC放到IO线程中。如果你的工作项目需要线程执行在可警告状态,你应该将它放到IO线程。
  非IO工作者线程等待在IO完成端口上,使用非IO线程比IO线程效率更高,也就是说,只要有可能的话,尽量使用非IO线程。IO线程和非IO线程在异步IO操作没有完成之前都不会退出。然而,不要在非IO线程中发出需要很长时间才能完成的异步IO请求。
  正确使用线程池的方法是,工作项目函数以及它将会调用到的所有函数都必须是线程池安全的。安全的函数不应该假设线程是一次性线程的或者是永久线程。一般来说,应该避免使用线程本地存储和发出需要永久线程的异步IO调用,比如说RegNotifyChangeKeyValue函数。如果需要在永久线程中执行这样的函数的话,可以给QueueUserWorkItem传递一个选项WT_EXECUTEINPERSISTENTTHREAD。
  注意,线程池不能兼容COM的单线程套间(STA)模型。
 
      为了更深入地讲解操作系统实现的线程池的优越性,我们首先尝试着自己实现一个简单的线程池模型。
 
      代码如下:
  /**//************************************************************************/
  /**//* Test Our own thread pool.                                            */
  /**//************************************************************************/
 
  typedef struct _THREAD_POOL
  {
      HANDLE QuitEvent;
      HANDLE WorkItemSemaphore;
 
      LONG WorkItemCount;
      LIST_ENTRY WorkItemHeader;
      CRITICAL_SECTION WorkItemLock;
 
      LONG ThreadNum;
      HANDLE *ThreadsArray;
 
  }THREAD_POOL, *PTHREAD_POOL;
 
  typedef VOID (*WORK_ITEM_PROC)(PVOID Param);
 
  typedef struct _WORK_ITEM
  {
      LIST_ENTRY List;
 
      WORK_ITEM_PROC UserProc;
      PVOID UserParam;
     
  }WORK_ITEM, *PWORK_ITEM;
 
 
  DWORD WINAPI WorkerThread(PVOID pParam)
  {
      PTHREAD_POOL pThreadPool = (PTHREAD_POOL)pParam;
      HANDLE Events[2];
     
      Events[0] = pThreadPool->QuitEvent;
      Events[1] = pThreadPool->WorkItemSemaphore;
 
      for(;;)
      {
          DWORD dwRet = WaitForMultipleObjects(2, Events, FALSE, INFINITE);
 
          if(dwRet == WAIT_OBJECT_0)
              break;
 
          //
          // execute user's proc.
          //
 
          else if(dwRet == WAIT_OBJECT_0 +1)
          {
              PWORK_ITEM pWorkItem;
              PLIST_ENTRY pList;
 
              EnterCriticalSection(&pThreadPool->WorkItemLock);
              _ASSERT(!IsListEmpty(&pThreadPool->WorkItemHeader));
              pList = RemoveHeadList(&pThreadPool->WorkItemHeader);
              LeaveCriticalSection(&pThreadPool->WorkItemLock);
 
              pWorkItem = CONTAINING_RECORD(pList, WORK_ITEM, List);
              pWorkItem->UserProc(pWorkItem->UserParam);
 
              InterlockedDecrement(&pThreadPool->WorkItemCount);
              free(pWorkItem);
          }
 
          else
          {
              _ASSERT(0);
              break;
          }
      }
 
      return 0;
  }
 
  BOOL InitializeThreadPool(PTHREAD_POOL pThreadPool, LONG ThreadNum)
  {
      pThreadPool->QuitEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
      pThreadPool->WorkItemSemaphore = CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL);
      pThreadPool->WorkItemCount = 0;
      InitializeListHead(&pThreadPool->WorkItemHeader);
      InitializeCriticalSection(&pThreadPool->WorkItemLock);
      pThreadPool->ThreadNum = ThreadNum;
      pThreadPool->ThreadsArray = (HANDLE*)malloc(sizeof(HANDLE) * ThreadNum);
 
      for(int i=0; i<ThreadNum; i++)
      {
          pThreadPool->ThreadsArray[i] = CreateThread(NULL, 0, WorkerThread, pThreadPool, 0, NULL);
      }
 
      return TRUE;
  }
 
  VOID DestroyThreadPool(PTHREAD_POOL pThreadPool)
  {
      SetEvent(pThreadPool->QuitEvent);
 
      for(int i=0; i<pThreadPool->ThreadNum; i++)
      {
          WaitForSingleObject(pThreadPool->ThreadsArray[i], INFINITE);
          CloseHandle(pThreadPool->ThreadsArray[i]);
      }
 
      free(pThreadPool->ThreadsArray);
 
      CloseHandle(pThreadPool->QuitEvent);
      CloseHandle(pThreadPool->WorkItemSemaphore);
      DeleteCriticalSection(&pThreadPool->WorkItemLock);
 
      while(!IsListEmpty(&pThreadPool->WorkItemHeader))
      {
          PWORK_ITEM pWorkItem;
          PLIST_ENTRY pList;
         
          pList = RemoveHeadList(&pThreadPool->WorkItemHeader);
          pWorkItem = CONTAINING_RECORD(pList, WORK_ITEM, List);
         
          free(pWorkItem);
      }
  }
 
  BOOL PostWorkItem(PTHREAD_POOL pThreadPool, WORK_ITEM_PROC UserProc, PVOID UserParam)
  {
      PWORK_ITEM pWorkItem = (PWORK_ITEM)malloc(sizeof(WORK_ITEM));
      if(pWorkItem == NULL)
          return FALSE;
 
      pWorkItem->UserProc = UserProc;
      pWorkItem->UserParam = UserParam;
 
      EnterCriticalSection(&pThreadPool->WorkItemLock);
      InsertTailList(&pThreadPool->WorkItemHeader, &pWorkItem->List);
      LeaveCriticalSection(&pThreadPool->WorkItemLock);
 
      InterlockedIncrement(&pThreadPool->WorkItemCount);
       ReleaseSemaphore(pThreadPool->WorkItemSemaphore, 1, NULL);
 
      return TRUE;
  }
 
  VOID UserProc1(PVOID dwParam)
  {
      WorkItem(dwParam);
  }
 
  void TestSimpleThreadPool(BOOL bWaitMode, LONG ThreadNum)
  {
      THREAD_POOL ThreadPool;   
      InitializeThreadPool(&ThreadPool, ThreadNum);
     
      CompleteEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
      BeginTime = GetTickCount();
      ItemCount = 20;
 
      for(int i=0; i<20; i++)
      {
          PostWorkItem(&ThreadPool, UserProc1, (PVOID)bWaitMode);
      }
     
      WaitForSingleObject(CompleteEvent, INFINITE);
      CloseHandle(CompleteEvent);
 
      DestroyThreadPool(&ThreadPool);
  }
      我们把工作项目放到一个队列中,用一个信号量通知线程池,线程池中任意一个线程取出工作项目来执行,执行完毕之后,线程返回线程池,继续等待新的工作项目。
      线程池中线程的数量是固定的,预先创建好的,永久的线程,直到销毁线程池的时候,这些线程才会被销毁。
      线程池中线程获得工作项目的机会是均等的,随机的,并没有特别的方式保证哪一个线程具有特殊的优先获得工作项目的机会。
      而且,同一时刻可以并发运行的线程数目没有任何限定。事实上,在我们的执行计算任务的演示代码中,所有的线程都并发执行。
      下面,我们再来看一下,完成同样的任务,系统提供的线程池是如何运作的。
 
  /**//************************************************************************/
  /**//* QueueWorkItem Test.                                                  */
  /**//************************************************************************/
 
  DWORD BeginTime;
  LONG  ItemCount;
  HANDLE CompleteEvent;
 
  int compute()
  {
      srand(BeginTime);
 
      for(int i=0; i<20 *1000 * 1000; i++)
          rand();
 
      return rand();
  }
 
  DWORD WINAPI WorkItem(LPVOID lpParameter)
  {
      BOOL bWaitMode = (BOOL)lpParameter;
 
      if(bWaitMode)
          Sleep(1000);
      else
          compute();
 
      if(InterlockedDecrement(&ItemCount) == 0)
      {
          printf("Time total %d second.\n", GetTickCount() - BeginTime);
          SetEvent(CompleteEvent);
      }
 
      return 0;
  }
 
  void TestWorkItem(BOOL bWaitMode, DWORD Flag)
  {
      CompleteEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
      BeginTime = GetTickCount();
      ItemCount = 20;
     
      for(int i=0; i<20; i++)
      {
          QueueUserWorkItem(WorkItem, (PVOID)bWaitMode, Flag);
      }   
 
      WaitForSingleObject(CompleteEvent, INFINITE);
      CloseHandle(CompleteEvent);
  }      很简单,是吧?我们仅需要关注于我们的回调函数即可。但是与我们的简单模拟来比,系统提供的线程池有着更多的优点。
      首先,线程池中线程的数目是动态调整的,其次,线程池利用IO完成端口的特性,它可以限制并发运行的线程数目,默认情况下,将会限制为CPU的数目,这可以减少线程切换。它挑选最近执行过的线程再次投入执行,从而避免了不必要的线程切换。
      系统提供的线程池背后的策略,我们下一节继续再谈。
 
  参考书目
 
  1,    MSDN Library
  2,    《Windows高级编程指南》
  3,    《Windows核心编程》
  4,    《Windows 2000 设备驱动程序设计指南》

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

再补一篇:

#ifndef _ThreadPool_H_
#define _ThreadPool_H_
#pragma warning(disable: 4530)
#pragma warning(disable: 4786)
#include <cassert>
#include <vector>
#include <queue>
#include <windows.h>

using namespace std;

class ThreadJob //工作基类
{
public:
//供线程池调用的虚函数
virtual void DoJob(void *pPara) = 0;
};
class ThreadPool
{
public:
//dwNum 线程池规模
ThreadPool(DWORD dwNum = 4) : _lThreadNum(0), _lRunningNum(0) 
{
InitializeCriticalSection(&_csThreadVector);
InitializeCriticalSection(&_csWorkQueue);
//手动还是自动
_EventComplete = CreateEvent(0, false, false, NULL);
_EventEnd = CreateEvent(0, true, false, NULL);
_SemaphoreCall = CreateSemaphore(0, 0, 0x7FFFFFFF, NULL);
_SemaphoreDel = CreateSemaphore(0, 0, 0x7FFFFFFF, NULL);
assert(_SemaphoreCall != INVALID_HANDLE_VALUE);
assert(_EventComplete != INVALID_HANDLE_VALUE);
assert(_EventEnd != INVALID_HANDLE_VALUE);
assert(_SemaphoreDel != INVALID_HANDLE_VALUE);
AdjustSize(dwNum <= 0 ? 4 : dwNum);
}
~ThreadPool()
{
DeleteCriticalSection(&_csWorkQueue);
CloseHandle(_EventEnd);
CloseHandle(_EventComplete);
CloseHandle(_SemaphoreCall);
CloseHandle(_SemaphoreDel);

vector<ThreadItem*>::iterator iter;
for(iter = _ThreadVector.begin(); iter != _ThreadVector.end(); iter++)
{
if(*iter)
delete *iter;
}
DeleteCriticalSection(&_csThreadVector);
}
//调整线程池规模
int AdjustSize(int iNum)
{
if(iNum > 0)
{
ThreadItem *pNew;
EnterCriticalSection(&_csThreadVector);
for(int _i=0; _i<iNum; _i++)
{
_ThreadVector.push_back(pNew = new ThreadItem(this)); 
assert(pNew);
pNew->_Handle = CreateThread(NULL, 0, DefaultJobProc, pNew, 0, NULL);
// set priority
SetThreadPriority(pNew->_Handle, THREAD_PRIORITY_BELOW_NORMAL);
assert(pNew->_Handle);
}
LeaveCriticalSection(&_csThreadVector);
}
else
{
iNum *= -1;
ReleaseSemaphore(_SemaphoreDel, iNum > _lThreadNum ? _lThreadNum : iNum, NULL);
}
return (int)_lThreadNum;
}
//调用线程池
void Call(void (*pFunc)(void *), void *pPara = NULL)
{
assert(pFunc);
EnterCriticalSection(&_csWorkQueue);
_JobQueue.push(new JobItem(pFunc, pPara));
LeaveCriticalSection(&_csWorkQueue);
ReleaseSemaphore(_SemaphoreCall, 1, NULL);//通知
}
//调用线程池
inline void Call(ThreadJob * p, void *pPara = NULL)
{
Call(CallProc, new CallProcPara(p, pPara));
}
//结束线程池, 并同步等待
bool EndAndWait(DWORD dwWaitTime = INFINITE)
{
SetEvent(_EventEnd);
return WaitForSingleObject(_EventComplete, dwWaitTime) == WAIT_OBJECT_0;
}
//结束线程池
inline void End()
{
SetEvent(_EventEnd);
}
inline DWORD Size()
{
return (DWORD)_lThreadNum;
}
inline DWORD GetRunningSize()
{
return (DWORD)_lRunningNum;
}
bool IsRunning()
{
return _lRunningNum > 0;
}
protected:
//工作线程
static DWORD WINAPI DefaultJobProc(LPVOID lpParameter = NULL)
{
ThreadItem *pThread = static_cast<ThreadItem*>(lpParameter);
assert(pThread);
ThreadPool *pThreadPoolObj = pThread->_pThis;
assert(pThreadPoolObj);
InterlockedIncrement(&pThreadPoolObj->_lThreadNum);
HANDLE hWaitHandle[3];
hWaitHandle[0] = pThreadPoolObj->_SemaphoreCall;
hWaitHandle[1] = pThreadPoolObj->_SemaphoreDel;
hWaitHandle[2] = pThreadPoolObj->_EventEnd;
JobItem *pJob;
bool fHasJob;

for(;;)
{
DWORD wr = WaitForMultipleObjects(3, hWaitHandle, false, INFINITE);
//响应删除线程信号
if(wr == WAIT_OBJECT_0 + 1)  
break;

//从队列里取得用户作业
EnterCriticalSection(&pThreadPoolObj->_csWorkQueue);
if(fHasJob = !pThreadPoolObj->_JobQueue.empty())
{
pJob = pThreadPoolObj->_JobQueue.front();
pThreadPoolObj->_JobQueue.pop();
assert(pJob);
}
LeaveCriticalSection(&pThreadPoolObj->_csWorkQueue);
//受到结束线程信号 确定是否结束线程(结束线程信号 && 是否还有工作)
if(wr == WAIT_OBJECT_0 + 2 && !fHasJob)  
break;
if(fHasJob && pJob)
{
InterlockedIncrement(&pThreadPoolObj->_lRunningNum);
pThread->_dwLastBeginTime = GetTickCount();
pThread->_dwCount++;
pThread->_fIsRunning = true;
pJob->_pFunc(pJob->_pPara); //运行用户作业
delete pJob; 
pThread->_fIsRunning = false;
InterlockedDecrement(&pThreadPoolObj->_lRunningNum);
}
}
//删除自身结构
EnterCriticalSection(&pThreadPoolObj->_csThreadVector);
pThreadPoolObj->_ThreadVector.erase(find(pThreadPoolObj->_ThreadVector.begin(), pThreadPoolObj->_ThreadVector.end(), pThread));
LeaveCriticalSection(&pThreadPoolObj->_csThreadVector);
delete pThread;
InterlockedDecrement(&pThreadPoolObj->_lThreadNum);
if(!pThreadPoolObj->_lThreadNum) //所有线程结束
SetEvent(pThreadPoolObj->_EventComplete);
return 0;
}
//调用用户对象虚函数
static void CallProc(void *pPara) 
{
CallProcPara *cp = static_cast<CallProcPara *>(pPara);
assert(cp);
if(cp)
{
cp->_pObj->DoJob(cp->_pPara);
delete cp;
}
}
//用户对象结构
struct CallProcPara  
{
ThreadJob* _pObj;//用户对象 
void *_pPara;//用户参数
CallProcPara(ThreadJob* p, void *pPara) : _pObj(p), _pPara(pPara) { };
};
//用户函数结构
struct JobItem 
{
void (*_pFunc)(void *);//函数
void *_pPara; //参数
JobItem(void (*pFunc)(void *) = NULL, void *pPara = NULL) : _pFunc(pFunc), _pPara(pPara) { };
};
//线程池中的线程结构
struct ThreadItem
{
HANDLE _Handle; //线程句柄
ThreadPool *_pThis; //线程池的指针
DWORD _dwLastBeginTime; //最后一次运行开始时间
DWORD _dwCount; //运行次数
bool _fIsRunning;
ThreadItem(ThreadPool *pthis) : _pThis(pthis), _Handle(NULL), _dwLastBeginTime(0), _dwCount(0), _fIsRunning(false) { };
~ThreadItem()
{
if(_Handle)
{
CloseHandle(_Handle);
_Handle = NULL;
}
}
};

std::queue<JobItem *> _JobQueue; //工作队列
std::vector<ThreadItem *> _ThreadVector; //线程数据
CRITICAL_SECTION _csThreadVector, _csWorkQueue; //工作队列临界, 线程数据临界


HANDLE _EventEnd, _EventComplete, _SemaphoreCall, _SemaphoreDel;//结束通知, 完成事件, 工作信号, 删除线程信号
long _lThreadNum, _lRunningNum; //线程数, 运行的线程数
};
#endif //_ThreadPool_H_


线程池思相: 有两个队列:线程队列与工作队列.线程队列里是开启的线程,工作队列是客户提交要完成的工作.线程队列里的线程会向工作队列里找工作做,如果有工作则去执行工作,否则阻塞.当有新工作加入,会唤醒阻塞线程,干活.给公司上班一样,几个人,有活干活,没活等活.

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

线程池使用 Windows NT 和 Windows 2000 操作系统系列下的可用完成端口。这种线程池的基本原理是创建一个单独的完成端口,使所有的工作线程在此完成端口上等待,当新请求进入时,将该请求发送至此完成端口。完成端口机制可确保只有一个控制线程获得这个新请求的通知。 

  如果不使用完成端口而使用其他同步对象(如信号灯),也可以创建并管理线程池。但是,使用完成端口时,操作系统会以最有效的方式调度和释放线程(即“后进先出”)。 

  其实可以把完成端口看成系统维护的一个队列,操作系统把重叠IO操作完成的事件通知放到该队列里,由于是暴露 “操作完成”的事件通知,所以命名为“完成端口”(COmpletion Ports)。一个socket被创建后,可以在任何时刻和一个完成端口联系起来。
  一般来说,一个应用程序可以创建多个工作线程来处理完成端口上的通知事件。工作线程的数量依赖于程序的具体需要。但是在理想的情况下,应该对应一个CPU创建一个线程。因为在完成端口理想模型中,每个线程都可以从系统获得一个“原子”性的时间片,轮番运行并检查完成端口,线程的切换是额外的开销。在实际开发的时候,还要考虑这些线程是否牵涉到其他堵塞操作的情况。如果某线程进行堵塞操作,系统则将其挂起,让别的线程获得运行时间。因此,如果有这样的情况,可以多创建几个线程来尽量利用时间。
  总之,开发一个可扩展的Winsock服务器并非十分困难的。主要是开始一个监听socket,接收连接,并且进行重叠发送和接收的IO操作。最大的挑战就是管理系统资源,限制重叠Io的数量,避免内存危机。遵循这几个原则,就能帮助你开发高性能,可扩展的服务程序。