作者:oldworm 可任意转载但请指明原始链接
服务器程序最核心的任务之一就是处理一组任务,在处理一组任务的时候最常见的做法是用线程池,最常见的线程池一般是由一组线程等待在一个信号灯上,有一个任务到达后解锁一个线程,让该线程去处理任务,线程处理完成后又回归到线程池,此做法比来一个任务分配一个线程的古老方法效率高了很多,但这也不是线程池的唯一做法,在windows下至少有三种典型线程池,由于实现上的不同效率相差很大,很有必要作一个比较,以便了解在什么情况下用什么线程池。Windows下常见的线程池实现有以下三种方式,分别是:基于信号灯的传统线程池(以下简称:传统线程池),系统线程池,完成端口线程池,下面分别介绍三种典型的线程池。
传统线程池
前面已经提到此线程池的典型做法是让一组线程等待在同一个信号灯上,一旦任务队列有新任务加入,池中某线程立刻被唤醒并从任务队列取出一个任务执行,完成后该线程回归到线程池中。
系统线程池
系统线程池是由win2000及之后的操作系统提供的,提供了方便的应用线程池的功能,用在以下几种场合:
Ø 以异步方式调用方法
Ø 以一定的时间间隔调用方法
Ø 当一个内核对象得到通知时,调用方法
Ø 当一个异步I/O请求完成时,调用方法
完成端口线程池
将完成端口和一组线程绑定,利用完成端口的排队和等待机制实现,由于操作系统对完成端口有专门的优化,我们期待这种模式的线程池有更好的表现。
上面简单介绍了三种典型线程池,但什么情况下用什么线程池,我们先来测试一下效率,后面再总结。我们的测试方法是使用以上三种典型线程池分别执行1000万小任务,比较执行任务的时间,我们用来测试的小任务是如下的一个函数:
void singletest(void *p)
{
volatile long l = 10;
for(int i=0; i<10; i++)
{
InterlockedIncrement(&l);
}
TESTNODE *pn = static_cast<TESTNODE *>(p);
if(InterlockedDecrement(&pn->tasknum) == 0)
SetEvent(gend);
}
一旦任务结束,置一个完成事件。为了测试简单,传统线程池和完成端口模式线程池都用了一个固定数目的线程,例子中用的是11,没有执行动态增减线程之类的调度操作。
经测试结果如下表:(耗时单位为毫秒)
测试次数
|
传统线程池耗时
|
系统线程池耗时
|
完成端口线程池耗时
|
1
|
49250
|
72234
|
20391
|
2
|
50906
|
71266
|
20281
|
3
|
50156
|
73000
|
20297
|
4
|
50437
|
71157
|
20000
|
5
|
49250
|
73078
|
19547
|
6
|
49844
|
73156
|
19469
|
耗时比约为:5 : 7 : 2,差别巨大
从上表可以看出,我们平时最习惯使用的传统线程池效率并不好,连完成端口线程池一半效率都不到,系统提供的线程池则效率更差,由此可以总结如下:
线程池模型
|
适用操作系统
|
效率
|
易用性
|
传统线程池
|
任意
|
中
|
较麻烦,大多数人写不出好的线程池
|
系统线程池
|
Win2000之后
|
低
|
最简单
|
完成端口线程池
|
Win2000之后
|
高
|
较简单
|
当然不是说系统提供的线程池一无是处,很多时候我们还是可以选择使用系统线程池的,毕竟它的使用是最简单的,只是需要最高效率的时候换个选择,大多数对效率要求不是特别高的场合或你要使用TimerQueue、RegisterWaitForSingleObject、完成端口例程等的时候必须要使用系统线程池,如果不考虑9x系统和nt,那么还是不要使用自己写的基于信号灯方式的传统线程池吧,既没效率也不简单。当然如果要考虑在各种平台下都通用,那么基于信号灯方式的传统线程池几乎是不二之选。
至于以上三种线程池效率差别为什么会这么大,我想各位看过这个数据的都会考虑,具体原因我将另文阐述。
测试代码如下:
// CompareThreadpool.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "CompareThreadpool.h"
#include "threadpool.h"
#include "threadgroup.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// The one and only application object
CWinApp theApp;
using namespace std;
HANDLE gend = INVALID_HANDLE_VALUE;
HANDLE giocp = INVALID_HANDLE_VALUE;
/**
该例子用来比较测试三种线程池,分别是:
我自己的线程池
系统提供的线程池
标准的完成端口模型
@author oldworm / oldworm@21cn.com
2007.5.22
*/
struct TESTNODE
{
volatile long tasknum; //任务总数目
DWORD start; //任务开始时间
DWORD end; //任务结束时间
};
void singletest(void *p)
{
volatile long l = 10;
for(int i=0; i<10; i++)
{
InterlockedIncrement(&l);
}
TESTNODE *pn = static_cast<TESTNODE *>(p);
if(InterlockedDecrement(&pn->tasknum) == 0)
SetEvent(gend);
// printf("num=%u, th=%u\r\n", pn->tasknum, GetCurrentThreadId());
}
void mythreadpool(void *p)
{
singletest(p);
}
DWORD WINAPI workitemfunc(void *param)
{
singletest(param);
return 0;
}
void thgroupfunc(void *pgroup, void *param)
{
// CThreadGroup *pg = static_cast<CThreadGroup *>(pgroup);
DWORD dwbyte;
TESTNODE *tn = NULL;
LPOVERLAPPED pov;
while(1)
{
GetQueuedCompletionStatus(giocp, &dwbyte, (LPDWORD)&tn, (LPOVERLAPPED*)&pov, INFINITE);
if(!tn) break;
singletest((void *)tn);
}
}
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
const int THREADS = 11;
const int TASKNUMS = 10000000;
CThreadPool tp(THREADS);
CThreadGroup tg;
TESTNODE tn;
int i;
gend = CreateEvent(NULL, FALSE, FALSE, NULL);
printf("线程池效率比较,分别执行%u个小任务\r\n", TASKNUMS);
//自己线程池方式
tn.start = GetTickCount();
tn.tasknum = TASKNUMS;
for(i=0; i<TASKNUMS; i++)
{
tp.Call(mythreadpool, &tn);
}
WaitForSingleObject(gend, INFINITE);
tn.end = GetTickCount();
printf("mythreadpool方式总耗时: %u\r\n", tn.end-tn.start);
////////////////////////////////////////////
//系统线程池方式
tn.start = GetTickCount();
tn.tasknum = TASKNUMS;
for(i=0; i<TASKNUMS; i++)
{
QueueUserWorkItem(workitemfunc, &tn, WT_EXECUTEDEFAULT);
}
WaitForSingleObject(gend, INFINITE);
tn.end = GetTickCount();
printf("系统线程池方式总耗时: %u\r\n", tn.end-tn.start);
////////////////////////////////////////////
//完成端口形式
tn.start = GetTickCount();
tn.tasknum = TASKNUMS;
giocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
for(i=0; i<THREADS; i++)
{
tg.AddThread(thgroupfunc, NULL);
}
tg.Start();
for(i=0; i<TASKNUMS; i++)
{
PostQueuedCompletionStatus(giocp, 0, (DWORD)&tn, NULL);
}
WaitForSingleObject(gend, INFINITE);
for(i=0; i<THREADS; i++)
{
PostQueuedCompletionStatus(giocp, 0, NULL, NULL);
}
tn.end = GetTickCount();
printf("完成端口方式总耗时: %u\r\n", tn.end-tn.start);
CloseHandle(giocp);
CloseHandle(gend);
return 0;
}