IOCP是在I / O发生时或之后运行的线程吗?

时间:2017-12-10 17:35:25

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

我正在尝试了解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应用程序有两种类型的线程池 - (1)“工作线程”和(2)“I / O线程”?
  • 如果是真的,是否有一个固定的数字,在配置中指定,比如M个工作线程和N个I / O线程?什么通常是M与N的比率?
  • 何时使用I / O线程?

1 个答案:

答案 0 :(得分:5)

两篇文章都以自己的方式正确。

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

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

  • 您在绑定到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自行排队项目,但这种情况不太常见。
  4. 纯托管异步操作(例如,使用Task.Delay执行async-await)不涉及任何IO句柄,因此它们最终不会被某个驱动程序发布到IOCP,因此这些操作会丢失在“工人”类别下。

    作为旁注,您可以通过其callstack告诉来自IO线程的Worker线程。 工作线程将使用“ThreadPoolWorkQueue.Dispatch”启动其托管调用堆栈,而IO线程将使用“_IOCompletionCallback.PerformIOCompletionCallback”启动其托管调用堆栈。这是可以随时更改的所有实现细节,但在调试托管代码时了解您正在处理的内容会很有帮助。