为什么 I/O 完成端口数据包按 FIFO 顺序排队,如果它们可能以不同的顺序出队?

Why are I/O Completion Port Packets Queued in FIFO order if they may be dequeued in a different order?

Microsoft 的 I/O Completion Ports 文档指出:

Please note that while the [completion] packets are queued in FIFO order they may be dequeued in a different order.

我的理解是线程通过调用GetQueuedCompletionStatus从完成端口获取完成包。如果系统不能保证数据包将按 FIFO 顺序检索,为什么系统会按 FIFO 顺序将数据包排队到完成端口?

您引用的声明旨在让您知道,如果在单个套接字上的 I/O 个完成之间需要排序,则您需要自己进行排序。如果您在单个套接字上发出多个 WSARecv 调用,您可能需要它。当他们完成时,完成将以 FIFO 顺序进入 IOCP 队列,这将是发出 WSARecv 调用的顺序。

如果您继续阅读该文档,您将看到这篇文章:

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.

这表明完成已按 FIFO 顺序从 IOCP 中删除。第一个注释的原因是,如果您有多个线程在等待 IOCP,那么线程调度问题可能意味着您的代码处理完成的顺序与从 IOCP 检索它们的顺序不同。

假设您有 2 个线程为一个 IOCP 服务,一个 TCP 套接字有 3 个待处理的 WSARecvs。来自网络的数据足以完成所有三个待处理的 WSARecvs,因此您最终在 IOCP 中完成了三个;我们称它们为 A、B 和 C。它们是按照 WSARecv 调用发出的顺序排列的,因此应该处理缓冲区 A、B 和 C 中的数据,以保持 TCP 流的完整性。

您的第一个 IOCP 线程将获得完成 A。第二个线程将获得完成 B。取决于您的硬件(内核数量等)和 OS 调度程序线程 1 或线程2 下一个可能到达 运行,或者两者都可能同时到达 运行。在上述情况下,这可能会给您带来麻烦。

我个人通过在编写可以在单个套接字上发出多个 WSARecvs 的服务器时向每个缓冲区添加一个序列号来解决这个问题。序列号递增,插入缓冲区并在同一个锁内发出 WSARecv,因此整个操作是原子的。当完成发生时,我确保只有一个线程处理给定套接字的缓冲区(参见 here) or I use a 'sequenced buffer collection' which can ensure that the buffers are processed in the correct sequence (see here)。

另请注意,为确保正确性,您无论如何都需要锁定在给定套接字上发出 WSARecv(和 WSASend)调用(参见 here

  1. 首先,如果可能,使缓冲区足够大以接收所有数据。
  2. 不要post 为一个套接字多次接收 WSARecv。
  3. 连接后,或读取数据后,处理这些日期,最后post WSARecv。

总和:任何时候一个套接字只有一个接收缓冲区。

我只为一个 iocp 句柄完成了服务器端、客户端、文件读取、写入、TCP 和 UDP 的 IOCP 服务器。