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

CRN*_*CRN 2 iocp

Microsoft 的I/O 完成端口文档指出:

请注意,虽然 [completion] 数据包按 FIFO 顺序排队,但它们可能会以不同的顺序出队。

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

Len*_*ate 5

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

如果您继续阅读该文档,您将看到以下内容:

阻止其在 I/O 完成端口上执行的线程以后进先出 (LIFO) 顺序释放,并且从该线程的 I/O 完成端口的 FIFO 队列中提取下一个完成数据包。这意味着,当一个完成数据包被释放到一个线程时,系统会释放与该端口关联的最后一个(最近的)线程,将最旧的 I/O 完成的完成信息传递给它。

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

想象一下,您有 2 个线程为一个 IOCP 提供服务,一个 TCP 套接字有 3 个 WSARecvs 挂起。来自网络的足够数据来完成所有三个挂起的 WSARecvs,所以你最终在 IOCP 中完成了三个;我们将它们称为 A、B 和 C。这些是按照发出 WSARecv 调用的顺序,因此应处理缓冲区 A、B 和 C 中的数据以保持 TCP 流的完整性。

您的第一个 IOCP 线程将获得完成 A。第二个线程将获得完成 B。根据您的硬件(内核数量等)和操作系统调度程序,线程 1 或线程 2 可能会在接下来运行,或者两者都可能会运行同时运行。这可能会导致您在上述情况下出现问题。

我个人通过在编写可以在单个套接字上发出多个 WSARecvs 的服务器时向每个缓冲区添加一个序列号来解决这个问题。序列号递增,插入缓冲区,并在同一个锁内发出 WSARecv,这样整个操作是原子的。当完成发生时,我确保只有一个线程处理给定套接字的缓冲区(请参阅此处),或者我使用“顺序缓冲区集合”,它可以确保以正确的顺序处理缓冲区(请参阅此处)。

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