您的位置:首页 > 运维架构 > 网站架构

Windows服务器端编程-第二章 设备IO与线程间通信-8-围绕I/O完成端口的架构

2007-07-31 11:27 267 查看
l 围绕I/O完成端口的架构
在服务应用程序初始化时,将会通过CreateNewCompletionPort之类的函数创建I/O完成端口。应用程序也需要创建一个线程池来处理客户端请求。现在要问的问题是:线程池内应该有多少线程?这个问题较难回答,所以将细节放到小节“线程池内有多少线程”中。迄今为止,一个标准的规则是CPU数量乘以2。因此,对于双CPU的机器,应该创建包含4个线程的线程池。

线程池内的所有线程应该执行同一个函数。典型地,该线程函数进行初始化工作,然后进行循环,直到服务进程收到停止的指令。在循环内,线程进入休眠状态,等待完成端口的设备I/O请求完成。调用GetQueeudCompletionStatus可以达到这个目的:

BOOL GetQueuedCompletionStatus(
HANDLE hCompPort,
PDWORD pdwNumBytes,
PULONG_PTR CompKey,
OVERLAPPED** ppOverlapped,
DWORD dwMilliseconds);


第一个参数,hCompPort,表示线程所关注的完成端口。许多服务应用程序使用单个I/O完成端口,并将所有的I/O请求通知完成到该端口。基本上,GetQueuedCompletionStatus的工作就是使线程进入休眠状态,直到指定的完成端口的I/O完成端口出现一个条目,或者指定的超时时间达到(由dwMilliseconds参数指定)。

第三个与I/O完成端口关联的数据结构是正在线程等待队列。线程池内每个调用GetQueuedCompletionStatus的线程的ID被放到正在线程等待队列中,以使I/O完成端口内核对象能够知道当前哪些线程正在等待处理完成的I/O请求。当在该完成端口的I/O完成队列中出现新的项时,完成端口从正在线程等待队列中挑出一个线程唤醒。被唤醒的线程将得到以下信息来组织一个已完成的I/O项:传输的字节数,完成键值,OVERLAPPED结构的地址。这些信息经由pdwNumBytes, pCompKey, ppOverlapped参数返回。

检查GetQueueCompletionStatus的返回原因有点麻烦;下面的代码演示了正确的方法:


DWORD dwNumBytes;
ULONG_PTR CompKey;
OVERLAPPED* pOverlapped;

// hIOCP 在程序的其他地方被初始化
BOOL fOk = GetQueuedCompletionStatus(hIOCP,
&dwNumBytes, &CompKey, &pOverlapped, 1000);
DWORD dwError = GetLastError();

if (fOk) {
// 成功处理了一个完成的I/O请求
} else {
if (pOverlapped != NULL) {
// 处理完成的I/O请求失败
// dwError 包含了失败的原因
} else {
if (dwError == WAIT_TIMEOUT) {
// 等待完成I/O项超时
} else {
// 对GetQueuedCompletionStatus的错误调用
// dwError 指出了错误调用的原因
}
}
}


正如所期望的那样,I/O完成队列中的项是以先进先出(FIFO)的方式删除的。但是,出乎意料的是,调用GetQueuedCompletionStatus的线程却是以后进先出(LIFO)的方式被唤醒。这么做的原因是为了提高性能。比如说,在线程等待队列中有四个线程,当已完成的I/O项出现时,最后一个调用GetQueuedCompletionStatus的线程将被唤醒来处理该项。这个最后的线程在处理完成后,又调用GetQueuedCompletionStatus重新进入线程等待队列。现在如果又出现了一个I/O完成项,同一线程又会被唤醒来处理新项。

当I/O请求的完成慢到单个线程都能够处理时,系统将一直唤醒同一线程进行处理,其他三个线程持续休眠。通过使用LIFO算法,没有被调度的线程可以将它们的内存资源(如堆栈空间)对换到磁盘并从进程的缓冲区内清空。这意味着即使众多线程在完成端口上等待也并非坏事。如果有几个线程在等待,但只有很少的I/O请求完成,多余的线程一定会将它们的大部分资源对换出系统。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐