首页
社区
课程
招聘
[翻译]I/O 完成端口
发表于: 2022-11-8 15:11 5681

[翻译]I/O 完成端口

2022-11-8 15:11
5681

在多核处理器的系统上,I/O 完成端口提供一种高效的线程处理模型来处理多个异步I/O请求.当进程创建一个I/O完成端口时,系统会为线程创建一个关联的队列对象,其唯一目的就是服务于I/O请求.使用I/O完成端口搭配预分配的线程池来处理多个并发异步I/O请求比接收到I/0请求的时候再创建线程更快更高效.

I/O完成端口是如何工作的?

CreateIoCompletionPort函数创建一个I/O完成端口,并且关联一个或者多个文件句柄.当这些文件句柄有异步I/O操作完成了,一个数据包就会以先进先出的顺序进入完成端口的队列.这个装置的一个高效的用法是将多个文件句柄的同步点组合为单个对象.请注意,数据包按先进先出顺序入队列,可能以不同的顺序出队列.

注意:

这里使用的术语:文件句柄,指的是表示重叠I/O端点的系统抽象,不仅仅是磁盘上的文件.比如,网络端点,TCP套接字,命名管道或邮槽.可以使用任何支持重叠I/O的系统对象.专题的结尾可以看到相关的函数列表.

 

当一个文件句柄和完成端口关联的时候,完成端口会变成阻塞的状态直到数据包被移除.唯一的例外是如果原始操作同步返回一个错误.线程(要么是主线程,要么是主线程创建的线程)使用GetQueuedCompletionStatus函数等待数据包被压入完成端口队列,而不是直接等待异步I/O完成.在I/0完成端口上阻塞的线程们,会按照后进先出(LIFO)的顺序被释放.下一个完成数据包将从该线程的I/O完成端口的FIFO队列中取出.这就是说,当一个完成包被释放给一个线程时,系统释放与该端口相关的最后一个(最近的)线程,并将最早的I/O完成的完成信息传递给它。

 

尽管多个线程都可以为某一个特定的I/O完成端口调用GetQueuedCompletionStatus函数,但当某个线程首次调用GetQueuedCompletionStatus时,它将与这个I/O完成端口相关联,直到发生以下三种情况之一:线程退出、线程被指定了另一个不同的I/O完成端口或关闭I/O完成端口。换句话说,单个线程最多只能与一个I/O完成端口相关联。

 

当一个完成数据包排队到一个I/O完成端口时,系统首先检查与该端口相关的线程的数量。如果运行的线程数小于并发值(下一节将讨论),则允许其中一个等待线程(最近的一个)处理完成包.当一个正在运行的线程完成它的处理时,它通常会再次调用GetQueuedCompletionStatus,这时它要么返回下一个完成包,要么等待队列为空。

 

线程可以使用PostQueuedCompletionStatus函数将完成包放入I/O完成端口的队列中.通过这样做,除了从I/O系统接收I/O完成数据包外,完成端口还可以用来接收来自当前进程中其他线程的数据.PostQueuedCompletionStatus函数允许应用程序将自己的专用完成数据包排队到I/O完成端口,而不启动异步I/O操作.这对于通知工作线程外部事件很管用.

 

I/O完成端口句柄和与该特定I/O完成端口关联的每个文件句柄称为对I/O完成端口的引用.当不再有I/O完成端口引用时,该I/O完成端口将被释放.因此,所有这些句柄必须适当关闭,以释放I/O完成端口及其相关的系统资源.满足这些条件后,应用程序应该通过调用CloseHandle函数来关闭I/O完成端口句柄.

注意:

I/O完成端口与创建它的进程相关联,不能在进程之间共享.但是,同一个进程中的线程之间可以共享单个句柄.

线程和并发

需要仔细考虑的I/O完成端口的最重要属性是并发值.完成端口的并发值在使用CreateIoCompletionPort函数创建时,由NumberOfConcurrentThreads参数指定.这个值限制了与完成端口相关联的可运行线程的数量.当与完成端口相关联的可运行线程总数达到并发值时,系统将阻塞与该完成端口相关联的任何后续线程的执行,直到可运行线程数低于并发值.

 

效率最高的情况发生在有完成数据包在队列中等待,但由于端口已达到并发限制而无法满足任何等待.考虑如下情况会发生什么:同个迸发值,多个线程都阻塞在GetQueuedCompletionStatus函数调用中.在这种情况下,如果队列总是有完成数据包在等待,当运行的线程调用GetQueuedCompletionStatus时,它不会阻塞执行,因为,如前所述,线程队列是后进先出.相反,这个线程将立即获取下一个排队的完成包.不会发生线程上下文切换,因为正在运行的线程不断地拾取完成包,而其他线程无法运行.

注意:

在前面的示例中,额外的线程似乎是无用的,而且从未运行过,但这假设正在运行的线程从未被其他机制置于等待状态、终止或以其他方式关闭其关联的I/O完成端口.在设计应用程序时考虑所有这样的线程执行分支.

 

并发值最佳值是计算机上的cpu数量.如果您的事务需要较长的计算时间,那么较大的并发值将允许运行更多的线程.每个完成包可能需要更长的时间来完成,但是将同时处理更多的完成包.您可以将并发值与分析工具结合使用,以为您的应用程序实现最佳效果.

 

系统还允许在GetQueuedCompletionStatus中等待的线程处理一个完成包,如果与同一I/O完成端口相关的另一个正在运行的线程由于其他原因进入等待状态,例如SuspendThread函数.当处于等待状态的线程再次开始运行时,可能会有一段短暂时间活动线程的数量超过并发值.然而,系统通过不允许任何新的活动线程运行,来快速减少这个数量,直到活动线程的数量低于并发性值.这是让应用程序在线程池中创建的线程多于并发值的一个原因.线程池管理超出了本主题的范围,但是一个好的经验法则是线程池中的线程数量至少是系统上处理器数量的两倍.

支持I/O的函数

以下函数可用于启动通过I/O完成端口完成的I/O操作.你必须传递给函数一个OVERLAPPED结构的实例和一个先前与I/O完成端口相关联的文件句柄(通过调用CreateIoCompletionPort)来启用I/O完成端口机制:
AcceptEx
ConnectNamedPipe
DeviceIoControl
LockFileEx
ReadDirectoryChangesW
ReadFile
TransactNamedPipe
WaitCommEvent
WriteFile
WSASendMsg
WSASendTo
WSASend
WSARecvFrom
LPFN_WSARECVMSG (WSARecvMsg)
WSARecv


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//