【问题标题】:Is an IOCP a thread that is running while the I/O is taking place or after?IOCP 是在 I/O 发生时或之后运行的线程吗?
【发布时间】:2018-05-24 07:18:12
【问题描述】:

我正在尝试了解 I/O 完成端口,特别是它们与使用 async-await 进行 I/O 的关系。

臭名昭著的文章There is No Thread 谈到在 I/O 完成后短暂借用 IOCP。因为这篇文章的全部意义在于表明,当花哨的硬件级 I/O 东西在进行中时,没有线程会被像这样的循环所消耗

I/O 完成了吗?没有。I/O 完成了吗?没有。I/O 完成了吗?没有。...

但后来我看到 this article 上面写着一个

"组件负责检查队列的完成端口 元素”

并给出一个类似

的例子
public class IOCompletionWorker
{ 
    public unsafe void Start(IntPtr completionPort)
    {
        while (true)
        {
            uint bytesRead;
            uint completionKey;
            NativeOverlapped* nativeOverlapped;

            var result = Interop.GetQueuedCompletionStatus(
                completionPort, 
                out bytesRead,
                out completionKey,
                &nativeOverlapped, 
                uint.MaxValue);

            var overlapped = Overlapped.Unpack(nativeOverlapped);

            if (result)
            {
                var asyncResult = ((FileReadAsyncResult)overlapped.AsyncResult);
                asyncResult.ReadCallback(bytesRead, asyncResult.Buffer);
            }
            else
            {
                ThreadLogger.Log(Interop.GetLastError().ToString());
            }

            Overlapped.Free(nativeOverlapped);
        }
    }
}

var completionPortThread = new Thread(() => new IOCompletionWorker().Start(completionPortHandle))
{
    IsBackground = true
};
completionPortThread.Start();

在我看来,正在进行一些民意调查。

我想我的问题可以归结为

  • 说 .NET 应用程序有 2 种类型的线程池 - (1)“工作线程”和 (2)“I/O 线程”是真的吗?
  • 如果是真的,是否有一个在配置中指定的固定数量,例如 M 个工作线程和 N 个 I/O 线程? M和N的比例通常是多少?
  • 具体何时使用 I/O 线程?

【问题讨论】:

  • 您的问题得到解答了吗? :)

标签: c# .net multithreading asynchronous async-await


【解决方案1】:

两篇文章都有各自的正确性。

IOCP 不是线程。它们可以被视为某种队列,内核(或常规用户模式代码,通过 PostQueuedCompletionStatus)可以在其中发布完成项。 IOCP 本身没有固有的线程模型或线程,它们只是多个生产者-消费者队列。

我们以网络 Sockets 为例,但对于任何类型的异步工作都是如此:

  • 您在绑定到 IOCP 的重叠模式套接字上调用 WSARecv,由网络驱动程序执行任何必要的操作来设置接收数据的实际请求。没有线程主动等待您的数据到达。
  • 数据到达。操作系统被硬件唤醒。操作系统会在内核中给网络驱动程序一些 CPU 时间来处理传入的事件。网络驱动程序处理中断,然后因为您的套接字绑定到 IOCP,所以将完成项发布到您的 IOCP 队列。请求已完成。

您的进程中没有实际的用户模式线程参与任何此操作(除了初始异步调用之外)。如果您想根据数据已到达这一事实采取行动(我假设您在从套接字读取时会这样做!),那么您必须将已完成的项目从 IOCP 中出列。

IOCP 的意义在于您可以将数千个 IO 句柄(套接字、文件等)绑定到单个 IOCP。然后,您可以使用单个线程来并行驱动这数千个异步进程。

是的,执行 GetQueuedCompletionStatus 的一个线程被阻塞,而 IOCP 上没有待处理的完成,所以这可能是您的困惑所在。但是 IOCP 的重点是您阻塞了一个线程,而您可以在任何给定时间等待数十万个网络操作,所有这些操作都由您的一个线程提供服务。您永远不会在 IO 句柄/IOCP/服务线程之间进行 1 对 1 对 1 的映射,因为那样您将失去异步的任何好处,而您还不如只使用同步 IO。

IOCP 的主要目的是在 Windows 下实现令人印象深刻的异步操作的并行性。

我希望这能澄清困惑。

至于具体问题

  1. 是的,.Net 框架有两个池。一种纯粹用于用户模式的通用工作,即“Worker”线程池。另一个是“IO”线程池。第二个是为了在编写高级 C# 代码时对您隐藏所有 IOCP 管理,从而使您的异步套接字像魔术一样工作。
  2. 这是可以随时更改的所有实现细节,但答案是两个池都是独立的。如果您在工作线程池上发生大量工作带宽,并且框架决定通过添加新线程来增加您的整体吞吐量,它将单独将线程添加到工作线程池,而不涉及 IO 池。 IO 池也是如此,如果你有行为不端的代码在它们的回调中阻塞 IO 线程,它将产生新的 IO 池而不触及工作池。您可以使用 ThreadPool.SetMinThreads/SetMaxThreads 自定义数字,但这通常表明您的进程滥用了线程池。
  3. 当有项目从线程池的内部 IOCP 出列时使用 IO 线程。在典型代码中,这将是在某个 IO 句柄上完成异步操作时。您也可以通过 UnsafeQueueNativeOverlapped 自己对项目进行排队,但这不太常见。

纯托管异步操作(例如,使用 Task.Delay 执行 async-await)不涉及任何 IO 句柄,因此它们最终不会被某些驱动程序发布到 IOCP,因此它们会下降在“工人”类别下。

附带说明一下,您可以通过调用堆栈将 Worker 线程与 IO 线程区分开来。 工作线程将使用“ThreadPoolWorkQueue.Dispatch”启动其托管调用堆栈,而 IO 线程将使用“_IOCompletionCallback.PerformIOCompletionCallback”启动其托管调用堆栈。这是所有可以随时更改的实现细节,但在调试托管代码时了解您正在处理的内容会很有帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-12-20
    • 1970-01-01
    • 2011-03-15
    • 1970-01-01
    • 1970-01-01
    • 2023-04-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多