如今是讨论线程池内应该有多少线程的恰当时机。从两个方面考虑。首先,在服务应用程序初始化时,要建立最少的线程集而没必要以正常方法建立和销毁。记住,建立和销毁线程会浪费CPU时间,所以进程的线程数量越小越好。其次,线程的数量要有一个最大值,建立过多的线程会浪费系统资源。即便大部部分资源能够被对换出内存,最小化对系统资源的使用以及不浪费页面空间仍然颇有优点,若是可以作到的话。算法
你可能想体会一下不一样数量的线程的结果。大多数服务(包括微软的IIS)使用启发式算法来管理它们的线程池。建议你也这么作。例如,能够如下的变量来管理线程池。架构
LONG g_nThreadsMin; // 线程池内线程的最小数量 LONG g_nThreadsMax; // 线程池内线程的最大数量 LONG g_nThreadsCrnt; //当前线程池内的线程数量 LONG g_nThreadsBusy; // 线程池内正在干活的线程数量
在应用程序初始化时,建立等于g_nThreadsMin的数量的线程,这些线程都执行相同线程池函数。如下伪代码展现了一个线程池函数:并发
DWORD WINAPI ThreadPoolFunc(PVOID pv) { // 线程正在进入线程池 InterlockedIncrement(&g_nThreadsCrnt); InterlockedIncrement(&g_nThreadsBusy); for (BOOL fStayInPool = TRUE; fStayInPool;) { // 线程中止执行并等待工做 InterlockedDecrement(&m_nThreadsBusy); BOOL fOk = GetQueuedCompletionStatus(...); DWORD dwIOError = GetLastError(); // 线程有工做要作,所以处于忙状态 int nThreadsBusy = InterlockedIncrement(&m_nThreadsBusy); // 应该加另外一个线程到线程池中么? if (nThreadsBusy == m_nThreadsCrnt) { // 全部的线程都在忙 if (nThreadsBusy < m_nThreadsMax) { // 线程池没有满 if (GetCPUUsage() < 75) { // CPU 使用率小于75% // 向池中添加线程 CloseHandle(chBEGINTHREADEX(...)); } } } if (!fOk && (dwIOError == WAIT_TIMEOUT)) { // 线程超时 if (!ThreadHasIoPending()) { // 没有太多的服务要作,所以线程能够结束,由于没有未完成的I/O请求 fStayInPool = FALSE; } } if (fOk || (po != NULL)) { // 线程被唤醒以进行处理,开始进行处理 ... if (GetCPUUsage() > 90) { // CPU 使用率超过 90% if (!ThreadHasIoPending()) { // 没有阻塞的I/O请求 if (g_nThreadsCrnt > g_nThreadsMin)) { // 当前线程数量大于最小值 fStayInPool = FALSE; // 从池中删除线程 } } } } } // 线程离开线程池 InterlockedDecrement(&g_nThreadsBusy); InterlockedDecrement(&g_nThreadsCurrent); return(0); }
这段伪码展现了使用完成端口时能够有多大的创造性。GetCPUUsage和ThreadHasIoPending函数并非Windows API。若是想使用它们,必须本身实现。另外,必须保证线程池内至少老是有一个线程,不然客户端将永远被挡在门外。能够上面的伪码为参考,不过特定的服务应用程序若是使用不一样的架构可能会有更好的效果。函数
注意工具
在本章的前面,小节“取消设备队列中的I/O请求”,曾说过系统会在线程终止时自动取消该线程产生的全部被阻塞的I/O请求。这就是为何伪码中像ThreadHasIoPending这样函数必需存在。若是线程仍有未完成的I/O请求,不要终止线程。
许多服务提供一个管理工具来容许管理员控制线程池的行为,例如,设置最大和最小线程数量,CPU使用率阈值,还有建立完成端口时指定的最大并发值。线程