我应该使用IOCP还是重叠的WSASend/Receive?

3
我正在调查在Windows上进行异步套接字I/O的选项。显然有不止一种选择:我可以使用带有完成回调或事件的重叠结构的WSASend...,或者我可以使用IOCP和(新的)线程池。从我通常阅读的内容来看,后者是推荐的选项。
但是,如果完成例程足以实现我的目标:告诉套接字发送这块数据并在完成时通知我,那么我为什么应该使用IOCP呢?
我理解,在与CreateThreadpoolIo等组合使用时,IOCP会使用操作系统线程池。然而,“普通”的重叠I/O也必须使用单独的线程吗?那么区别/劣势是什么?我的回调由I/O线程调用并阻塞其他东西吗?
提前感谢, Christoph
2个回答

2
您可以使用任何一种方式,但是对于服务器来说,使用带有“完成队列”的IOCP通常具有更好的性能,因为它可以使用多个客户端<>服务器线程,可以使用CreateThreadpoolIo或一些用户空间线程池。显然,在这种情况下,通常会使用专用的处理程序线程。
在我看来,重叠完成例程I/O对于客户端更有用。完成例程由异步过程调用触发,该调用排队到发起I/O请求的线程中(WSASend、WSARecv)。这意味着该线程必须处于处理APC的位置,通常意味着在某个“blahEx()”调用周围循环while(true)。这可能很有用,因为很容易等待阻塞队列或其他线程间信号,允许向线程提供要发送的数据,并且完成例程始终由该线程处理。这种I/O机制使'hEvent' OVL参数保持空闲-非常适合将通信缓冲区对象指针传递到完成例程中。
应避免使用实际同步事件/信号量/任何其他重叠hEvent参数的I/O。

为什么应该避免同步事件方法? - nitzanms

0

Windows IOCP文档建议每个完成端口的可用核心最多使用一个线程。超线程会使核心数量翻倍。由于使用IOCP导致几乎可以被视为事件驱动的应用程序,使用线程池会给调度器增加不必要的处理。

如果你仔细想一想,就能理解为什么了:一个事件应该尽快完整地服务(或在初始处理后放入某个队列中)。假设有五个事件排队到一个4核电脑上的IOCP。如果与IOCP关联的线程有八个,你就有可能让调度器中断一个事件来开始服务另一个事件,使用另一个线程来处理,这是低效的。而且,如果被中断的线程在临界区内,这样做还可能是危险的。有四个线程时,你可以同时处理四个事件,并在完成一个事件后立即开始处理IOCP队列中剩下的最后一个事件。

当然,你可能会为非IOCP相关的处理设置线程池。

编辑________________

套接字(文件句柄也可以)与IOCP相关联。完成例程在IOCP上等待。一旦从套接字请求的读取或写入完成,操作系统通过IOCP释放等待在IOCP上的完成例程,并返回您调用读取或写入时提供的附加信息(我通常传递指向控制块的指针)。因此,完成例程立即“知道”在哪里找到与完成相关的信息。

如果您传递了引用控制块(类似的)的信息,则该控制块(可能)需要跟踪已完成的操作,以便知道下一步该做什么。IOCP本身既不知道也不关心。

如果您正在编写连接到互联网的服务器,则服务器将发出读取以等待客户端输入。该输入可能会在一毫秒或一周后到达,当它到达时,IOCP将释放完成例程并分析输入。通常,它会响应包含输入中请求的数据的写入,然后等待IOCP。当写入完成时,IOCP再次释放完成例程,该例程看到写入已完成,(通常)发出新的读取并开始新的循环。

因此,基于IOCP的应用程序通常在完成发生之前几乎不会消耗任何CPU资源(或者根本不会),在完成例程开始全力处理直到完成处理、发送新的I/O请求并再次等待完成端口的过程中才会消耗大量CPU资源。除了IOCP超时(可用于信号处理或其他操作)外,所有与I/O相关的操作都在操作系统中进行。

为了进一步复杂化(或简化)事情,使用WSA例程服务套接字并非必要,Win32函数ReadFile和WriteFile同样可以正常工作。


1
这一切都是建立在你的线程工作不会阻塞的前提下(这是理想情况,但并非总是可能的)。如果线程可能被阻塞,则让额外的线程等待IOCP可能有所帮助。通过分析和观察可以得出结论。如果您想确保在共享池的其他潜在用户之前处理自己的I/O,则拥有自己的线程池可能会有所帮助。理想情况下,每个“连接”(完成键)对象都有自己的内部队列,因此如果对同一个“连接”获得多个完成(读取和写入?),则不会使用两个IOCP线程,并使它们争夺同一个“连接”。 - Len Holgate
@Olof:所以您仍然建议使用IOCP而不是重叠I/O,但我应该将单个线程专用于完成端口。我理解得对吗?这是否会限制我应该使用ExistingCompletionPort参数关联到端口的句柄数量? - Christoph
IOCP是重叠I/O,但它们比等待多个对象(本质上是一种轮询形式)要高效得多。您在系统中等待IOCP的线程数不应超过核心数(具有超线程的核心计为两个)。因此,如果您有四个核心,则不应该有超过四个线程在等待IOCP。当然,如果您有两个IOCP,则每个IOCP不应该有超过四个线程(并且可能需要调整线程优先级,就像我所做的那样)。 - Olof Forshell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接