I/O completion ports provide an efficient threading model for processing multiple asynchronous I/O requests on a multiprocessor system. When a process creates an I/O completion port, the system creates an associated queue object for requests whose sole purpose is to service these requests.Processes that handle many concurrent asynchronous I/O requests can do so more quickly and efficiently by using I/O completion ports in conjunction with a pre-allocated thread pool than by creating threads at the time they receive an I/O request.
IO完成端口在多处理器系统上提供一个高效的线程模型来处理并发异步IO请求。当一个进程创建一个IO完成端口后,系统会创建一个与之关联的队列对象来专门为这些请求服务。进程在预先分配的线程池的帮助下,会快速高效的处理并发的异步请求,这比来一个请求再去创建一个线程好多了。
How I/O Completion Ports Work 完成端口怎么工作
The CreateIoCompletionPort function Creates an IO completion port and associates one or more file handles with that port.When an asynchronous IO operation on one of these file handles completes, an IO completion packet is queued in first-in-first out(FIFO) order to the associated IO completion port.One powerful use for this mechanism is to combine the synchronization point for multiple file handles into a single object, although there are also other useful applications.
CreateIoCompletionPort函数创建一个完成端口并将多个文件句柄与之关联。当某个句柄上的一个异步IO操作完成,一个IO完成包以先进先出的顺序放到关联的完成端口里。这样的一个明显的好处是将多个文件句柄的异步操作合并到一个对象上,当然还有一些其它有用的好处。
Note
The term file handle as used here refers to system abstraction representing an overlapped IO endpoint, not only a file on disk.For example, it can be a network endpoint, TCP socket, named pipe, or mail slot.Any system object that supports overlapped IO can be used.For a list of related IO functions, see the end of this topic.
注意
这里的文件句柄表示的是代表重叠IO类型的一个抽象,并不仅仅是磁盘上的文件。例如,还可以是网络端点,TCP socket,命名管道,或者邮槽。任何支持重叠IO的系统对象都可以用。相关的IO函数,可以参看这个主题的尾部。
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 IO completion port, rather than waiting directly for the asynchronous IO to complete.Threads that block their execution on an IO completion port are released in last-in-first-out(LIFO) order, and the next completion packet is pulled from the IO 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 IO completion.
一个线程(被主线程创建的或主线程本身)使用GetQueuedCompletionStatus函数等待一个完成包插入到完成端口里,而不是直接等待异步IO的完成。被完成端口阻塞的线程采取后进先出的顺序释放,下一个完成包被从完成端口的FIFO队列中提取出来给刚释放的线程。这也就是说,当一个完成包被解压给一个线程时,系统释放最近与完成端口关联的线程,给它传递等待最久的完成包。
Although any number of threads can call GetQueuedCompletionStatus for a specified IO completion port, when a specified thread calls GetQueuedCompletionStatus the first time, it becomes associated with the specified IO completion port until one of three things occurs: The thread exits, specifies a different IO completion port, or closes the IO completion port.In other words, a single thread can be associated with, at most, one IO completion port.
尽管任意线程可以调用GetQueuedCompletionStatus来查询一个特定的IO完成端口,但一个特定的线程第一次调用GetQueuedCompletionStatus时,这个线程就会与特定的完成端口关联直到下面三件事之一发生:线程退出,标识一个不同的IO完成端口,或者关闭IO完成端口,换句话说,一个单一的线程只能也最多与一个IO完成端口关联。
When a completion packet is queued to an IO completion port, the system first checks how many threads associated with that port are running.If the number of threads running is less than the concurrency value(discussed in the next section), one of the waiting threads(the most recent one) is allowed to process the completion packet.When a running thread completes its processing, it typically calls GetQueuedCompletionStatus again, at which point it either returns with the next completion packet or waits if the queue is empty.
当一个完成包被插入到完成端口里,系统首先检查多少个与这个完成端口关联的线程正在运行。如果运行线程的数量小于并发值(在下节讨论), 一个等待的线程(最晚被置为等待状态)被允许去处理完成包。当一个运行的线程完成它的处理时,它照例再一次调用GetQueuedCompletionStatus,此时线程或者得到下一个完成包或者一直等待如果队列是空的话。
Threads can use the PostQueuedCompletionStaus function to place completion packets in an IO completion port's queue. By doing so, the completion port can be used to receive communications from other threads of the process, in addition to receiving IO completion packets from the IO system.The PostQueuedCompletionStatus function allows an application to queue its own special-purpose completion packets to the IO completion port without starting an asynchronous IO operation.This is useful for notifying worker threads of external events, for example.
线程可以用PostQueuedCompletionStatus函数来放置完成包到一个IO完成端口的队列中。通过这样做,除了可以从IO系统中接收IO完成包外,完成端口还可以被用于接收进程中其它线程的消息。PostQueuedCompletionStatus函数允许一个程序来投递它自己的有特别用途的完成包到IO系统而不需要启动一个异步IO操作。这样很有用,例如,可以用于通知工作线程额外的事件。
The IO completion port handle and every file handle associated with that particular IO completion port are known as references to the IO completion port.The IO completion port is released when there are no more references to it.Therefore, all of these handles must be properly closed to release the IO completion port and its associated system resources.After these conditions are satisfied, an application should close the I/O completion port handle by calling the CloseHandle function.(这段不用翻译了,就是关闭句柄,前篇翻译过了。)
Threads and Concurrency
The most important property of an IO completion port to consider carefully is the concurrency value.The concurrency value of a completion port is specified when it is created with CreateIoCompletionPort via the NumberOfConcurrentThreads parameter.This value limits the number of runnable threads associated with the completion port.When the total number of runnable threads associated with the completion port reaches the concurrency value, the system blocks the execution of any subsequent threads associated with that completion port until the number of runnable threads drops below the concurrency value.
线程和并发
仔细斟酌,会发现并发值是完成端口最重要的属性。完成端口的并发值在调用CreateIoCompletionPort时通过参数NumberOfConcurrentThreads这个参数传递进来。这个值限制与完成端口关联的可运行线程数量。当与完成端口关联的可运行线程数量达到并发值时,系统阻塞所有与完成端口关联的后续线程的执行,直到可运行线程的数量降到并发值以下。
The most efficient scenario occurs when there are completion packets waiting in the queue, but no waits can be satisfied because the port has reached its concurrency limit.Consider what happens with a concurrency value of on an multiple threads waiting in the GetQueuedCompletionStatus function calls.In this case, if the queue always has completion packets waiting, when the running thread calls GetQueuedCompletionStatus, it will not block execution because, as mentioned earlier, the thread queue is LIFO.Instead, this thread will immediately pick up the next queued completion packet.No thread context switches will occur, because the running thread is continually picking up completion packets and the other threads are unable to run.
最高效的场景是:有完成包在队列里等待,但是没有一个等待可以被处理,因为完成端口已经达到了并发限制。考虑下面情况会发生什么:很多线程在并发值限制的情况下等待在GetQueuedCompletionStatus的调用处。在这种情况下,如果队列中总是有完成包在等待处理,当运行的线程调用GetQueuedCompletionStatus,这个线程将不会阻塞,因为就像前面说的,线程队列是后进先出。相反,这个线程会立即拿到下一个队列中的完成包。不会发生线程切换,因为运行的线程在连续不断的处理完成包而其他线程不能运行。
Note
In the previous example, the extra threads appear to be useless and never run, but that assumes that the running thread never gets put in a wait state by some other mechanism, terminates, or otherwise closes its associated IO completion port.Consider all such thread execution ramifications when desining the application.
注意
在前面的例子中,额外的线程看起来没什么用,一直不能运行,但这样的前提是运行的线程永远不会等待,终止,或者用别的方法关闭与其关联的完成端口。当设计程序时,要考虑所有这些线程执行情况。
The best overall maximum value to pick for the concurrency value is the number of CPUs on the computer.If your transaction required a lengthy computation, a larger concurrency value will allow more threads to run.Each completion packet may take longer to finish, but more completion packets will be processed at the same time.You can experiment with the concurrency value in conjunction with profiling tools to achieve the best effect for your application.
大体上说,最合适的并发值是计算机的CPU数量。如果你的事务需要很长的计算时间,一个大的并发值可以允许更多的线程运行。每个完成包可能花很多时间来处理,但是多个完成包可以同时被处理。你可以用不同的分析工具测试不同的并发值来为你的应用程序挑选最合适的值。