完成端口模型
“完成端口”模型是迄今为止最为复杂的一种 I / O模型。然而,假若一个应用程序同时需
要管理为数众多的套接字,那么采用这种模型,往往可以达到最佳的系统性能!但不幸的是,
该模型只适用于Windows NT和Windows 2000操作系统。因其设计的复杂性,只有在你的应用
程序需要同时管理数百乃至上千个套接字的时候,而且希望随着系统内安装的 C P U数量的增
多,应用程序的性能也可以线性提升,才应考虑采用“完成端口”模型。要记住的一个基本
准则是,假如要为Windows NT或Windows 2000开发高性能的服务器应用,同时希望为大量套
接字I / O请求提供服务(We b服务器便是这方面的典型例子),那么I / O完成端口模型便是最佳
选择!
从本质上说,完成端口模型要求我们创建一个Wi n 3 2完成端口对象,通过指定数量的线程,
对重叠I / O请求进行管理,以便为已经完成的重叠I / O请求提供服务。要注意的是,所谓“完成
端口”,实际是Wi n 3 2、Windows NT以及Windows 2000采用的一种I / O构造机制,除套接字句
柄之外,实际上还可接受其他东西。然而,本节只打算讲述如何使用套接字句柄,来发挥完
成端口模型的巨大威力。使用这种模型之前,首先要创建一个 I / O完成端口对象,用它面向任
意数量的套接字句柄,管理多个I / O请求。要做到这一点,需要调用CreateIoComletionPort函数。
该函数定义如下:
HANDLE CreateIoCompletionPort (
HANDLE FileHandle, // handle to file
HANDLE ExistingCompletionPort, // handle to I/O completion port
ULONG_PTR CompletionKey, // completion key
DWORD NumberOfConcurrentThreads // number of threads to execute concurrently
);
在我们深入探讨其中的各个参数之前,首先要注意该函数实际用于两个明显有别的目的:
■ 用于创建一个完成端口对象。
■ 将一个句柄同完成端口关联到一起。
最开始创建一个完成端口时,唯一感兴趣的参数便是 NumberOfConcurrentThreads(并发
线程的数量);前面三个参数都会被忽略。NumberOfConcurrentThreads参数的特殊之处在于,
它定义了在一个完成端口上,同时允许执行的线程数量。理想情况下,我们希望每个处理器
各自负责一个线程的运行,为完成端口提供服务,避免过于频繁的线程“场景”切换。若将
该参数设为0,表明系统内安装了多少个处理器,便允许同时运行多少个线程!可用下述代码
创建一个I / O完成端口:
CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)
该语句的作用是返回一个句柄,在为完成端口分配了一个套接字句柄后,用来对那个端
口进行标定(引用)。
1. 工作者线程与完成端口
成功创建一个完成端口后,便可开始将套接字句柄与对象关联到一起。但在关联套接字
之前,首先必须创建一个或多个“工作者线程”,以便在I / O请求投递给完成端口对象后,为
完成端口提供服务。在这个时候,大家或许会觉得奇怪,到底应创建多少个线程,以便为完
成端口提供服务呢?这实际正是完成端口模型显得颇为“复杂”的一个方面,因为服务 I / O请
求所需的数量取决于应用程序的总体设计情况。在此要记住的一个重点在于,在我们调用
CreateIoComletionPort时指定的并发线程数量,与打算创建的工作者线程数量相比,它们代表
的并非同一件事情。早些时候,我们曾建议大家用 CreateIoComletionPort函数为每个处理器
都指定一个线程(处理器的数量有多少,便指定多少线程)以避免由于频繁的线程“场景”
交换活动,从而影响系统的整体性能。CreateIoComletionPort函数的NumberOfConcurrentThreads参数明确指示系统:在一个完成端口上,一
次只允许 n个工作者线程运行。假如在完
成端口上创建的工作者线程数量超出 n个,那么在同一时刻,最多只允许 n个线程运行。但实
际上,在一段较短的时间内,系统有可能超过这个值,但很快便会把它减少至事先在
CreateIoComletionPort函数中设定的值。那么,为何实际创建的工作者线程数量有时要比
CreateIoComletionPort函数设定的多一些呢?这样做有必要吗?如先前所述,这主要取决于
应用程序的总体设计情况。假定我们的某个工作者线程调用了一个函数,比如 Sleep或
WaitForSingleObject,但却进入了暂停(锁定或挂起)状态,那么允许另一个线程代替它的位
置。换言之,我们希望随时都能执行尽可能多的线程;当然,最大的线程数量是事先在
CreateIoCompletonPort调用里设定好的。这样一来,假如事先预计到自己的线程有可能暂时
处于停顿状态,那么最好能够创建比 CreateIoCompletonPort的NumberOfConcurrentThreads参数的值多的线程,以便到时候充分发挥系统的潜