I/O Completion PortsI/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.
I/O完成端口提供在一个多核处理器系统上面处理多个异步I/O请求的高效线程模型.
当创建一个I/O 完成端口时,操作系统创建与I/O完成端口相关联的队列对象来处理这些I/O请求.
I/O完成端口使用和一个预先分配好的线程池相结合,而不是接收I/O请求时才创建线程的方式来高效快速的处理多个并发异步I/O请求.
How I/O Completion Ports Work
The CreateIoCompletionPort function creates an I/O completion port and associates one or more file handles with that port.
When an asynchronous I/O operation on one of these file handles completes, an I/O completion packet is queued in first-in-first-out (FIFO) order to the associated I/O 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.
Please note that while the packets are queued in FIFO order they may be dequeued in a different order.
CreateIoCompletionPort()可以创建一个I/O 完成端口并且可以和一个或者多个Socket相管理.
当这些异步Socket完成时,一个I/O 完成端口数据包将按照先进先出(FIFO)顺序在完成端口上排队.
注意:虽然数据包按照FIFL顺序排队,但是不一定按照FIFL顺序出队列.
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.
当一个Socket和一个完成端口相关联时,直到the packet 从完成端口移除时,阻塞状态才被更新.
一个线程使用GetQueuedCompletionStatus()函数来等到数据包在完成端口上面排队,而不是等到异步I/O操作的完成.
在I/O完成端口上面的阻塞线程将按照后进先出顺序被释放,然而,数据包是按照FIFL顺序从完成端口上弹出等待线程处理.
这就意味着,当一个数据包从队列中弹出时,将是线程池中最后一个被释放的线程.
Although any number of threads can call GetQueuedCompletionStatus for a specified I/O completion port,
when a specified thread calls GetQueuedCompletionStatus the first time, it becomes associated with the specified I/O completion port until one of three things occurs:
1 The thread exits,
2 specifies a different I/O completion port
3 closes the I/O completion port
In other words, a single thread can be associated with, at most, one I/O completion port.
当指定的线程第一次调用GetQueuedCompletionStatus()时,线程将和指定的完成端口相关联,直到下面任意事件的发生:
1 The thread exits,
2 specifies a different I/O completion port
3 closes the I/O completion port
换言之,一个线程最多和一个完成端口相关联
When a completion packet is queued to an I/O 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 , 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.
当一个Completion Packet 在完成端口排队时,操作系统首先检查在线程池中和完成端口关联的正在运行的线程.
当正在运行的线程数量小于指定的并发值,等待的线程将被允许处理Completion Packet.
当一个运行中的线程处理完Completion Packet后,将再次调用GetQueuedCompletionStatus()处理下一个Completion Packet.如果等待队列为空,GetQueuedCompletionStatus()将阻塞.
Threads can use the PostQueuedCompletionStatus function to place completion packets in an I/O 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 I/O completion packets from the I/O system.
The PostQueuedCompletionStatus function allows an application to queue its own special-purpose completion packets to the I/O completion port without starting an asynchronous I/O operation
This is useful for notifying worker threads of external events, for example.
Threads and Concurrency
The most important property of an I/O 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()函数创建IOCP对象时,通过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 one and multiple threads waiting in the GetQueuedCompletionStatus function call.
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()函数,该线程不会阻塞立即执行,并继续处理下一个数据包.因为线程是LILO 后进先出顺序.
因为一个运行的线程可以立刻处理完成端口的数据包,所有其他线程将不能运行,因为不存在线程上下文切换.