用于 I/O 的最新 Windows 线程池 API 使用情况

Posted

技术标签:

【中文标题】用于 I/O 的最新 Windows 线程池 API 使用情况【英文标题】:Latest Windows threadpool API usage for I/O 【发布时间】:2016-07-28 05:05:59 【问题描述】:

我不了解最新的 Windows 线程池 API 的一部分。我需要这方面的帮助。

从文档中,将它用于 I/O(在我的例子中,用于 SOCKET)的方法可以总结如下:

    调用 CreateThreadpoolIo。 致电StartThreadpoolIo。您可以在此处找到此警告:

    您必须在对绑定到 I/O 完成对象的文件句柄上启动每个异步 I/O 操作之前调用此函数。否则将导致线程池在完成时忽略 I/O 操作并导致内存损坏。

    调用文件句柄上的操作(例如,WSARecvFrom)。如果失败,请致电CancelThreadpoolIo。否则,在结果可用时处理结果。 WSARecvFrom,当异步使用时,要求 WSAOVERLAPPED(您必须事先创建)但不要求任何将其链接到对 StartThreadpoolIo 的先前调用的信息。 CancelThreadpoolIo 只要求 PTP_IO,而不要求任何其他信息来派生特定的异步操作。 重复步骤 2 和 3。 调用 CloseThreadpoolIo 完成。您可以在此处找到此警告:

    可能需要取消线程池 I/O 通知以防止内存泄漏。有关详细信息,请参阅 CancelThreadpoolIo。

我通常需要它用于 UDP,因此我努力在任何给定时间将多个接收操作排队(异步 WSARecvFrom 操作开始)。这样我就不必在回调函数开始时急于启动另一个接收操作,也不必同步对接收缓冲区的访问(我可以拥有一个池,每个都可以包含一个数据报,然后重新发出接收操作当我完成处理每条消息时;在此期间,其他排队的操作将使接收者保持忙碌)。数据报是独立的和自包含的。我知道这种方法可能对 TCP 无效。

StartThreadpoolIo/CancelThreadpoolIo 在我看来是问题的根源:StartThreadpoolIo 和 WSARecvFrom 没有直接绑定(它们不共享任何参数)。所以:

框架如何知道调用CancelThreadpoolIo时要取消哪个操作?它如何只取消失败的操作而不取消任何待处理的操作?

你可以说,“不要同时调用 StartThreadpoolIo”。我可以没有几个并发的 WSARecvFrom,但我不能没有并发的 WSARecvFrom 和 WSASendTo。所以我认为不能同时进行多个异步操作不是 API 的设计方式。

您可以说,“只调用一次 StartThreadpoolIo,就足以注册回调;这是一个开/关过程”。但是文档说:

您必须在对文件句柄启动每个异步 I/O 操作之前调用此函数...

您可以说,“它取消了由刚刚调用 StartThreadpoolIo 的同一线程启动的操作”。但是在调用 CloseThreadpoolIo 的上下文中调用 CancelThreadpoolIo 的建议没有意义(我将从触发停止的线程调用 CloseThreadpoolIo,这将完全独立于发出异步操作的线程;并且对 CancelThreadpoolIo 的单个调用可能不足以取消几个操作)。无论如何,无法从不同的线程触发取消是一个严重的限制。我知道 CreateThreadpoolCleanupGroup 的存在,但我的问题更为根本。我想了解这个 API 如何从根本上说是正确和有用的。

您可以说“多次调用 CreateThreadpoolIo,以便您可以使用独立的 PTP_IO”。它不起作用。当我第二次调用 CreateThreadpoolIo 时,返回 nullptr。

我错了,还是这个 API 很尴尬?通常,其他异步 API 使用以下模式之一:

    创建操作并接收句柄 => 调用传递句柄的方法。 创建可重复使用的句柄 => 调用传递句柄的方法(包括启动操作)。

最新的 Windows 线程池 API,其中句柄似乎是隐式的,或者同一操作有多个句柄(TP_IO、WSAOVERLAPPED、StartThreadpoolIo)并且它们没有显式链接在一起,它们都不使用。

非常感谢您的帮助。

【问题讨论】:

从我阅读的文档来看,调用 CancelThreadPoolIo 实际上并没有取消 I/O 操作,毕竟这已经完成了。它只是告诉线程池不要期待它的完成通知。换句话说,我的猜测是线程池需要保持对给定文件句柄上挂起的 I/O 操作的计数,而 StartThreadPoolIo/CancelThreadPoolIo 都是为了保持计数正确。 >> 当我第二次调用 CreateThreadpoolIo 时,返回 nullptr。这是因为基于线程池的 IO 下的底层机制。在内部,这个 CreateThreadpoolIo() 事物将完成端口与您的句柄相关联,并且 StartThreadpoolIo)( 保证有一个线程池线程准备好并在完成端口上等待。每个句柄不能有两个完成端口。如果你想更好了解 Vista 线程池的工作原理,仔细研究 WIndows 2000 线程池的工作原理(如 BindIoCompletionCallback()),并思考如何编写更好的抽象 @HarryJohnston 你能证实你对柜台的怀疑吗? @LeandroCaniglia,我从来没有看到任何尝试的意义——毕竟,这是一个实现细节,可能会发生变化。根据我的评论和谢尔盖的回答,如何使用 API 似乎很清楚,这才是真正重要的。 【参考方案1】:

当你调用 CancelThreadpoolIo 时,框架如何知道要取消哪个操作?它如何仅取消失败的操作 而不是任何未决的?

CancelThreadpoolIo() 不会取消 IO。它与 StartThreadpoolIo() 互为倒数。 StartThreadpoolIo() 准备线程池以接受完成。如果线程池不期望完成,它不会等待它,因此您可能会错过它。如果线程池期望完成但没有完成,线程池可能会浪费资源。

CancelThreadpoolIo() 撤消 StartThreadpoolIo() 所做的一切。

【讨论】:

“因此你可能会错过它”——在我的情况下,错过是第二次机会的 AV,这不是很有趣。真正的问题是,Start- 调用是否堆积起来,Cancel- 是否只撤消该堆中的一个 - 或Start- 只是将状态更改为“准备回调”,无论调用多少次,Cancel-撤消了这一切。直觉上我宁愿相信增量-减量图,但我宁愿看到它明确地写在文档中。

以上是关于用于 I/O 的最新 Windows 线程池 API 使用情况的主要内容,如果未能解决你的问题,请参考以下文章

C#进阶系列27 I/O限制的异步操作

线程池大小选择:针对 I/O 密集型场景和 CPU 密集型场景

36 | Tomcat I/O和线程池的并发调优

线程池(ThreadPool)

线程池线程数与(CPU密集型任务和I/O密集型任务)的关系

Day703.Tomcat I/O和线程池的并发调优 -深入拆解 Tomcat & Jetty