为什么 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)
- 首先,如果可能,使缓冲区足够大以接收所有数据。
- 不要post 为一个套接字多次接收 WSARecv。
- 连接后,或读取数据后,处理这些日期,最后post WSARecv。
总和:任何时候一个套接字只有一个接收缓冲区。
我只为一个 iocp 句柄完成了服务器端、客户端、文件读取、写入、TCP 和 UDP 的 IOCP 服务器。
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)
- 首先,如果可能,使缓冲区足够大以接收所有数据。
- 不要post 为一个套接字多次接收 WSARecv。
- 连接后,或读取数据后,处理这些日期,最后post WSARecv。
总和:任何时候一个套接字只有一个接收缓冲区。
我只为一个 iocp 句柄完成了服务器端、客户端、文件读取、写入、TCP 和 UDP 的 IOCP 服务器。