RegisterWaitForSingleObject
使用 I/O 完成端口与仅使用让线程池线程等待 I/O 完成有什么区别?
其中一个更快,如果是,为什么?
RegisterWaitForSingleObject
使用 I/O 完成端口与仅使用让线程池线程等待 I/O 完成有什么区别?
其中一个更快,如果是,为什么?
IOCP 通常是执行速度最快的 IO 周转机制,原因有一个:阻塞检测。
一个简单的例子是负责从磁盘提供文件的服务器。IOCP 通常由三个主要部分组成:
N和M在这方面的区别非常重要。一般的理念是将 M 配置为机器上的核心数,并将 N 配置为更大。多大取决于您的工作线程在阻塞状态下花费的时间。如果您正在读取磁盘文件,您的线程将被绑定到磁盘 IO 通道的速度。当您拨打该电话时,ReadFile()
您刚刚引入了一个阻塞电话。如果 M == N,那么一旦你命中所有读取磁盘文件的线程,你就会完全停滞不前,所有线程都在磁盘 IO 通道上。
但是,如果某些花哨的调度程序有办法“知道”该线程 (a) 参与 IOCP 线程池,并且 (b) 只是因为它发出了一个耗时的 API 调用而停止了呢?如果发生这种情况时,那个花哨的调度程序可以暂时将该线程“移动”到一个特殊的“运行但停止”组,然后“释放”一个在线程停止时自愿工作的额外线程怎么办?
这正是IOCP 带来的。当 N大于M 时,IOCP 会将刚刚发出停顿的线程置于特殊的 running-but-stalled 状态,然后暂时从您的 N 池中“借用”一个额外的线程。它将继续这样做,直到N 池已用尽,或者被停止的线程开始从其阻塞请求中返回。
因此,在这种情况下,一个 IOCP 配置为在 8 核机器上同时运行 8 个线程实际上可能在实际池中拥有数百个线程。只有 8 个将被“允许”同时在非阻塞状态下运行,尽管当阻塞线程从它们的块返回并且您已经借用线程为其他请求提供服务时,您可能会暂时弹出它。
GetQueueCompletionStatus()
最后,虽然对您的原因没有那么重要,但它仍然很重要:如果 IOCP 线程在完成当前工作并发出下一个调用时队列上有待处理的工作,则不会阻塞,也不会进行上下文切换。如果有工作在等待,它将捡起它并继续执行,而无需强制抢占。当然,操作系统调度器无论如何都可以抢占,但只是作为通用调度器的一部分;不是因为特定的调用GetQueueCompletionStatus()
。唯一的例外是,如果已经有超过 M 个线程在运行且未阻塞。在这种情况下,GetQueueCompletionStatus()
将阻塞调用线程,直到当足够多的线程再次被阻塞时再次需要它来进行松弛工作。
您提供的描述表明您将受到严重的磁盘 io 限制。对于绝对性能关键的 io-server 架构,几乎不可能超越 IOCP 的优势,尤其是操作系统级别的块检测,它允许调度程序知道它可以暂时从主池中释放额外的线程来保留东西在其他线程停止时泵送。
您根本无法使用 Windows 线程池复制 IOCP 的特定功能。如果您的所有线程都是很少或没有 IO 的数字处理器,我会说线程池会更合适,但您对磁盘 IO 的特殊性告诉我您应该改用 IOCP。