I/O Completion Ports学习
表示还是自己看MSDN最直接,别人的介绍都是嚼剩下,有木有?
IO完成端口为在多处理器系统处理多个异步IO请求提供一个高效的线程模型。当一个进程新建一个完成端口,操作系统新建一个目的为服务这些请求的队列对象。通过利用IO完成端口与相关联的预先分配的线程池而不是新建线程来处理当前请求,处理多个并发的异步IO请求会更快更有效。
函数CreateIoCompletionPort创建一个IO完成端口,并在这个端口上关联一个或多个file【文件描述符】。当在这些操作符上的一个或者多个的一个异步IO操作完成时,一个IO完成包(packet)就进入与IO完成端口关联的先进先出队列。这个机制的一个强大的用法是在单个对象里进行多个描述符间的同步,尽管他们还有其他有用的应用。请注意:在队列中的数据包可能会以不同的顺序出列。【这句不怎么理解,是不是多个线程在出列操作?】
注意:
属于file handle是代表重叠IO端点的一种系统抽象代表,而不只是磁盘上的file。例如,可以是网络端点,TCP socket,命名管道或者mail slot。任何支持完成端口的系统对象都可以被利用。
When a file handle is associated with a completion port, the status block passed in will not be updated until the packet is removed from the completion port. The only exception is if the original operation returns synchronously with an error. A thread (either one created by the main thread or the main thread itself) uses the GetQueuedCompletionStatus function to wait for a completion packet to be queued to the I/O completion port, rather than waiting directly for the asynchronous I/O to complete. Threads that block their execution on an I/O completion port are released in last-in-first-out (LIFO) order, and the next completion packet is pulled from the I/O completion port's FIFO queue for that thread. This means that, when a completion packet is released to a thread, the system releases the last (most recent) thread associated with that port, passing it the completion information for the oldest I/O completion.
当描述符与一个完成端口相关联,传进来的状态快不会被更新直到包从完成端口上拿走。唯一的异常是原来的操作异步返回错误。一个线程(可以是主线程创建的或者主线程自己)用GetQueuedCompletionStatus 函数来等待进入完成端口的包,而不是直接等待异步IO完成。在完成端口阻塞操作的线程会以后进显出释放,而这个线程的下一个端口包会以先进先出队列从IO端口取出。这就意味着,当一个包喂给了一个线程,系统将释放最后一个与端口关联的线程,同时为最老的完成端口传递完成端口信息【这句没怎么看懂】。
尽管任何线程都能向特定的完成端口调用GetQueuedCompletionStatus,当一个具体的线程第一次调用它时,它开始与特定的完成端口关联知道以下三种情况中的一种发生:
1.线程存在,关联了另外一个完成端口;
2.关闭了完成端口。
也就是说一个线程只能关联一个完成端口。
当一个完成包被放入完成端口的队列中,系统首先检查当前有多少与端口关联的线程。如果运行中的线程数目小于并发值,最近的一个等待线程被允许处理完成包。当一个运行线程完成处理,它将要继续再次调用GetQueuedCompletionStatus,因为在这个点它要么等待下一个完成包,要么等待到包队列为空。
线程可以调用PostQueuedCompletionStatus来投递完成包到IO端口的队列。通过这样做,完成端口可以用来收取其他线程或者进程的交互数据,还可以从IO系统收取完成端口包。PostQueuedCompletionStatus函数允许应用程序不开启异步IO操作,就能向IO完成端口投递特定的完成包。比如,这对于以外不事件通知工作线程是有用的。
IO完成端口句柄和每个描述符句柄与特定IO完成端口关联被认为提领了完成端口,当没有与它关联的引用时完成端口被释放。因此,所有这些句柄必须被适合地关心以释放完成端口和它关联的系统资源。这些条件满足后,通过调用CloseHandle来关闭完成端口句柄。
注意
一个完成端口与创建它的进行关联,不能被进程间共享。然而,单个句柄可以在同一进程的不同线程中共享。
IO完成端口的最重要的性质是并发值。完成端口的并发值在通过CreateIoCompletionPort的theNumberOfConcurrentThreads参数被确定。这个值限制了与完成端口相关联的线程的数目。当关联线程数目超过了这个并发值,系统将会阻塞任何之后的线程,直到运行线程数目降到并发值。
最有效的语义是当有完成包在队列里,但没有等待会被满足因为端口已经达到并发限制。考虑到在并发值下,用一个或多个线程等待在GetQueuedCompletionStatus调用。在这种情况下,如果队列有完成包在等待,当运行线程调用GetQueuedCompletionStatus,他将不会阻塞,因为如上面提到的线程队列是先进先出的。取而代之的是,这个线程将立即拿到下一个完成包。没有上下文切换发生,因为运行线程连续地拿完成包,另外的线程不能运行【这段都怎么理解】。
注意
在前面的例子里,额外的线程好像是无用的,并且不会允许,但假设运行线程因为某些同步机制从未进入等待状态,停止或者关闭它关联的完成端口。注意当重新设计应用的时候所有这些线程的执行分支。
最好的并发最大值是电脑的CPU个数。如果你的传输要求一个复杂的计算,这就需要大点的并发值。每个完成包可能完成的时间增加,但更多的完成包被并发地处理。你可以试验设置并发值来达到程序的最优化。
系统运行当一关联同一完成端口的线程因为一些原因(例如SuspendThread函数)在等待状态时,另一线程等待在GetQueuedCompletionStatus来处理完成包。当这个在等待状态的线程开始运行,当活跃线程数目超过并发值这将是一个短暂的过程。然而,系统将通过不允许任何启动新的活跃线程迅速减少活跃线程数目,直到活跃线程数目下降到并发值。这也是你可以在线程池里创建比并发值多的线程的原因。线程池管理超越了本话题的范围,但是一个好的经验是在线程池里最少拥有系统处理器两倍的线程数(>=2*corenum)。可以参考 Thread Pools。
下列的函数可以被用来通过利用完成端口来启动IO操作。你必须传递OVERLAPPED结构的一个实例和之前与IO关联的描述符给这些函数(通过调用CreateIoCompletionPort)来开启IO完成端口机制:
- ConnectNamedPipe
- DeviceIoControl
- LockFileEx
- ReadDirectoryChangesW
- ReadFile
- TransactNamedPipe
- WaitCommEvent
- WriteFile
- WSASendMsg
- WSASendTo
- WSASend
- WSARecvFrom
- WSARecvMsg
- WSARecv
ok,翻译完了,很多句子不通顺啊,有的地方自己都不明白啊!