线程池实现.docx
《线程池实现.docx》由会员分享,可在线阅读,更多相关《线程池实现.docx(41页珍藏版)》请在冰豆网上搜索。
线程池实现
Afxpool.exe是一个自解压缩的可执行文件,其中包含演示为基于MFC的ISAPI扩展创建线程池的方法的一个MicrosoftVisualC++6.0项目所需的文件。
本文解释在ISAPI扩展内创建工作线程池的步骤。
通常,当ISAPI扩展需要大量处理时间来完成一个请求时,需要创建单独的控制线程来处理该过程。
有关其他信息,请参见以下Microsoft知识库文章:
185518如何使用MFCISAPI扩展实现工作线程
但是,如果为每个新请求都创建一个新的控制线程,则会对服务器的性能产生很大影响。
因此,最好是创建一个可以“回收”线程的工作线程池。
这种方法的作用是,将在处理新请求的开销中除去创建新线程的开销。
ISAPI扩展在第一次初始化时将发生创建线程的开销,但是对于每个请求,它将只从线程池中请求可用的工作线程。
回到顶端
可以从Microsoft下载中心下载以下文件:
Afxpool.exe
有关如何下载Microsoft支持文件的其他信息,请单击下面的文章编号,以查看Microsoft知识库中相应的文章:
119591如何从联机服务获取Microsoft支持文件
Microsoft已对此文件进行了病毒扫描。
Microsoft使用的是该文件发布时可以获得的最新病毒检测软件。
该文件存储在安全性得到增强的服务器上,以防止在XX的情况下对其进行更改。
文件名大小
---------------------------------------------------------
ExtThreadPool.aps18,436
ExtThreadPool.clw420
ExtThreadPool.cpp6,184
ExtThreadPool.def173
ExtThreadPool.dsp4,332
ExtThreadPool.dsw549
ExtThreadPool.h1,560
ExtThreadPool.ncb66,560
ExtThreadPool.opt54,784
ExtThreadPool.plg723
ExtThreadPool.rc3,242
ExtThreadPool.rc2405
Resource.h322
StdAfx.cpp215
StdAfx.h669
ThreadPool.cpp3,130
ThreadPool.h863
线程池使用WindowsNT和Windows2000操作系统系列下的可用完成端口。
这种线程池的基本原理是创建一个单独的完成端口,使所有的工作线程在此完成端口上等待,当新请求进入时,将该请求发送至此完成端口。
完成端口机制可确保只有一个控制线程获得这个新请求的通知。
如果不使用完成端口而使用其他同步对象(如信号灯),也可以创建并管理线程池。
但是,使用完成端口时,操作系统会以最有效的方式调度和释放线程(即“后进先出”)。
在典型的ISAPI扩展中,“ISAPIMFC向导”将创建两个虚函数:
GetExtensionVersion和TerminateExtension。
完成端口的初始化是在GetExtensionVersion中通过调用CreateIoCompletionPort来完成的。
请注意,使用此函数的最后一个参数
,您可以指定可同时从线程池中运行的线程数。
最有效的方法是使运行的线程数与CPU数相同。
最后一个参数如果是0,则表示允许操作系统选取与CPU数相匹配的线程数。
BOOLCExtThreadPoolExtension:
:
GetExtensionVersion(
HSE_VERSION_INFO*pVer)
{
//Calldefaultimplementationforinitialization
CHttpServer:
:
GetExtensionVersion(pVer);
//Loaddescriptionstring
TCHARsz[HSE_MAX_EXT_DLL_NAME_LEN+1];
ISAPIVERIFY(:
:
LoadString(AfxGetResourceHandle(),
IDS_SERVER,sz,HSE_MAX_EXT_DLL_NAME_LEN));
_tcscpy(pVer->lpszExtensionDesc,sz);
m_phThreads=newHANDLE[THREAD_COUNT];
m_nThreadCount=THREAD_COUNT;
m_hIoPort=CreateIoCompletionPort((HANDLE)INVALID_HANDLE_VALUE,
NULL,0,0);
//NOTE:
IfaCWinAppobjectisnotdeclare,thiscallshouldbe
//changedtoCreateThread()instead.
for(longn=0;n<m_nThreadCount;n++)
{
CWinThread*pWinThread=AfxBeginThread(
(AFX_THREADPROC)ThreadProc,m_hIoPort);
m_phThreads[n]=pWinThread->m_hThread;
}
returnTRUE;
}
此处,在CExtThreadPoolExtension类中定义了三个变量:
m_nThreadCount、m_hIoPort和m_phThreads。
m_nThreadCount是长整型,用于定义在线程池中创建的线程数(在ThreadPool.h文件中将该值硬编码为THREAD_COUNT变量)。
m_hIoPort是单独的完成端口句柄,您应将其发送给所有工作线程进行等待。
m_phThreads是来自工作线程的线程句柄数组。
应当注意的是,本示例是在共享库中使用MFC构建的。
如果使用向导将MFCISAPI扩展创建为静态链接库,则CWinApp的实例将不可用。
因此,AfxBeginThread()调用应当替换为CreateThread()。
虽然“MFCISAPI向导”创建了GetExtensionVersion和TerminateExtension方法,但是还需要手动替代CHttpServer的HttpExtensionProc()方法。
这可以通过在CExtThreadPoolExtension声明中添加该方法来完成:
classCExtThreadPoolExtension:
publicCHttpServer
{
public:
CExtThreadPoolExtension();
~CExtThreadPoolExtension();
//Overrides
//ClassWizardgeneratedvirtualfunctionoverrides
//NOTE-theClassWizardwilladdandremovemember
//functionshere.
//DONOTEDITwhatyouseeintheseblocksofgeneratedcode!
//{{AFX_VIRTUAL(CExtThreadPoolExtension)
public:
virtualBOOLGetExtensionVersion(HSE_VERSION_INFO*pVer);
virtualDWORDHttpExtensionProc(EXTENSION_CONTROL_BLOCK*pECB);
//}}AFX_VIRTUAL
virtualBOOLTerminateExtension(DWORDdwFlags);
//TODO:
Addhandlersforyourcommandshere.
//Forexample:
voidDefault(CHttpServerContext*pCtxt);
DECLARE_PARSE_MAP()
//{{AFX_MSG(CExtThreadPoolExtension)
//}}AFX_MSG
protected:
HANDLE*m_phThreads;
HANDLEm_hIoPort;
longm_nThreadCount;
};
这是必须的,因为必须替代CHttpServer:
:
HttpExtensionProc()返回的值,以使IIS知道您仍在处理此请求,而不回收EXTENSION_CONTROL_BLOCK。
替代HttpExtensionProc的代码相当简单:
DWORDCExtThreadPoolExtension:
:
HttpExtensionProc(
EXTENSION_CONTROL_BLOCK*pECB)
{
//Theresultofthebaseclass's(CHttpServer)HttpExtensionProc()
//mustbechecked.Ifit'ssuccessful,youwillneedtoreturna
//HSE_STATUS_PENDING.ThisissothatIISwillnotshutdownthe
//connectionandnotdestroytheECB.
//Iftheresultofthebaseclassisnotsuccessful,youshould
//simplypropagatetheerrormessageuptoIIS.
DWORDdwResult=CHttpServer:
:
HttpExtensionProc(pECB);
if(dwResult==HSE_STATUS_SUCCESS)
returnHSE_STATUS_PENDING;
else
returndwResult;
}
只须检查从CHttpServer:
:
HttpExtensionProc()中返回的调用,如果成功,则返回pending。
否则,仅传送出现的错误。
在Default()实现(或在消息映射中MFCISAPI扩展调用的任一例程)中,提取EXTENSION_CONTROL_BLOCK(请注意,这与CHttpServerContext对象不同;不能将CHttpServerContext对象传递给工作线程,因为该对象仅存在于CHttpServer:
:
HttpExtensionProc()作用域内),并将其投递到在GetExtensionVersion()中创建的完成端口:
voidCExtThreadPoolExtension:
:
Default(CHttpServerContext*pCtxt)
{
if(!
PostQueuedCompletionStatus(m_hIoPort,
(DWORD)pCtxt->m_pECB,0,NULL))
{
charszBuffer[4096]={0};
sprintf(szBuffer,"<H1>Errorpostingtocompletionport!
Win32Error=%i</H1>",GetLastError());
DWORDdwBufLen=strlen(szBuffer);
pCtxt->m_pECB->WriteClient(pECB->ConnID,szBuffer,
&dwBufLen,0);
}
}
如果完成端口投递失败,则需要生成一条错误消息,并将其重新报告给客户端。
请注意,要输出这一错误,请从ECB(而不是MFC包装程序)中直接使用WriteClient回调。
此外,本示例还演示将各种参数传递给工作线程的方法。
除了Default()外,还有ProcessName()和ProcessSample()。
ProcessName()采用三个字符串参数:
Firstname、Lastname和Middlename。
ProcessSample采用两个参数:
Data和String。
Data将是整型数据,String是字符串型数据。
其目的是利用MFCISAPI扩展的分析映射支持,并允许工作线程处理适当的请求。
要实现此目的,需创建以下两种结构:
structProcessNameData
{
EXTENSION_CONTROL_BLOCK*pECB;
CStringsFirstName;
CStringsLastName;
CStringsMiddleName;
};
structProcessSampleData
{
EXTENSION_CONTROL_BLOCK*pECB;
DWORDnValue;
CStringsString;
};
ProcessNameData结构将用于临时存放由MFC分析映射传递给ProcessName()函数的参数。
在ProcessName()函数内,动态分配一个新的ProcessNameData结构,并设置EXTENSION_CONTROL_BLOCK以及传递给我们的参数,然后将其传递给工作线程。
但是,将数据传递给工作线程时,必须使工作线程知道所传递的数据为ProcessNameData类型。
这可通过PostQueuedCompletionStatus()API中的第三个参数来实现。
在Default()函数中,将0作为第三个参数进行传递。
对于ProcessName(),传递的是1:
voidCExtThreadPoolExtension:
:
ProcessName(CHttpServerContext*pCtxt,
LPCTSTRszFirstName,
LPCTSTRszLastName,
LPCTSTRszMiddleName)
{
ProcessNameData*pData=newProcessNameData;
pData->pECB=pCtxt->m_pECB;
pData->sFirstName=szFirstName;
pData->sLastName=szLastName;
pData->sMiddleName=szMiddleName;
if(!
PostQueuedCompletionStatus(m_hIoPort,(DWORD)pData,
(DWORD)1,NULL))
{
charszBuffer[4096]={0};
sprintf(szBuffer,
"<H1>Errorpostingtocompletionport!
Win32Error=%i</H1>",
GetLastError());
DWORDdwBufLen=strlen(szBuffer);
pCtxt->m_pECB->WriteClient(pCtxt->m_pECB->ConnID,szBuffer,
&dwBufLen,0);
}
}
通过将值1传递给工作线程,工作线程将知道它需要处理的数据与Default()例程不同。
同样,对于ProcessSample()函数传递值2。
在此函数中,应创建一个新的ProcessSampleData结构实例。
在ThreadProc(工作线程所执行的实际例程)中,需要使所有线程阻塞在一个单独的完成端口(从GetExtensionVersion传递给线程例程)。
当线程调用GetQueuedCompletionStatus()时将结束这种状况。
如果未投递任何状态,则调用将一直阻塞,直至投递一个状态为止。
投递完成端口后,其中的一个工作线程将“醒来”并开始处理:
unsignedintThreadProc(void*p)
{
HANDLEIoPort=(HANDLE*)p;
unsignedlongpN1,pN2;
OVERLAPPED*pOverLapped;
while(GetQueuedCompletionStatus(IoPort,&pN1,&pN2,
&pOverLapped,INFINITE))
{
if(pOverLapped==(OVERLAPPED*)0xFFFFFFFF)
break;
else
{
switch(pN2)
{
case0:
DoWork((EXTENSION_CONTROL_BLOCK*)pN1);
break;
case1:
DoWork((ProcessNameData*)pN1);
break;
case2:
DoWork((ProcessSampleData*)pN1);
break;
default:
//Huh?
!
?
!
?
break;
}
}
}
return0;
}
可以看到,在switch()语句中,对正确的pN2值(该值是在PostQueuedCompletionStatus()调用中传递的第三个参数)进行了测试。
另外,还需要指出的是,DoWork()存在三个版本。
一个版本将采用EXTENSION_CONTROL_BLOCK指针,另一个版本采用ProcessNameData指针,而第三个版本采用ProcessSampleData指针。
通过显式地强制转换pN1,可以确保为提供的数据调用了正确的版本。
对于采用ProcessNameData和ProcessSampleData的DoWork()版本,需要在退出之前破坏这些结构。
这是因为这些结构创建的目的只是用于临时存放分析映射参数:
voidDoWork(ProcessNameData*pData)
{
EXTENSION_CONTROL_BLOCK*pECB=pData->pECB;
CStringsBody;
//BuildtheHTMLbody...
sBody.Format(
"<H1>Thisisthread%iinthreadpool.Hithere!
</H1>",
GetCurrentThreadId());
sBody+="<HR><H2>I'mprocessingthisfor";
sBody+=pData->sFirstName;
sBody+="";
if(pData->sMiddleName.CompareNoCase("~")!
=0)
{
sBody+=pData->sMiddleName;
sBody+="";
}
sBody+=pData->sLastName;
sBody+="</H2>";
//Pretendwe'respendingsometimeprocessingthisrequest
//thissampleassumes3seconds.
Sleep(3000);
SendData(pECB,sBody);
deletepData;
}
voidDoWork(ProcessSampleData*pData)
{
EXTENSION_CONTROL_BLOCK*pECB=pData->pECB;
CStringsBody;
//BuildtheHTMLbody...
sBody.Format(
"<H1>Thisisthread%iinthreadpool.Hithere!
</H1>",
GetCurrentThreadId());
sBody+="<HR><H2>Thisisthesampledata:
";
sBody+=pData->sString;
CStringsTmp;
sTmp.Format("(%i)</H2>",pData->nValue);
sBody+=sTmp;
//Pretendwe'respendingsometimeprocessingthisrequest
//thissampleassumes3seconds.
Sleep(3000);
SendData(pECB,sBody);
deletepData;
}
SendData()是一个将CString发送回客户端的简单函数。
但是,在开始处理之前,需要确保没有向线程发出终止信号。
这可通过测试pOverLapped指针的值来完成。
如果该重叠指针的值是0xFFFFFFFF(无效的句柄值),则说明线程将终止。
因此,例程将跳出while循环。
要向工作线程发出终止信号,可使用通过“MFCISAPI向导”创建的第三个虚函数:
TerminateExtension()。
在此函数中,需要向所有线程发出关闭信号,同时关闭完成端口:
BOOLCExtThread
PoolExtension:
:
TerminateExtension(DWORDdwFlags)
{
//extensionisbeingterminated
for(longn=0;n<m_nThreadCount;n++)
PostQueuedCompletionStatus(m_hIoPort,0,
0,(OVERLAPPED*)0xFFFFFFFF);
WaitForMultipleObjects(m_nThreadCount,m_phThreads,TRUE,120000);
CloseHandle(m_hIoPort);
delete[]m_phThreads;
returnTRUE;
}
要向所有线程发出关闭信号,可使用无效句柄值投递一个重叠指针。
对可用线程数执行此操作。
这将确保每个线程都收到该通知。
完成此操作后,通过对从AfxBeginThread()调用返回的句柄(这些句柄复制自