74

在 .NET 中很难找到对工作线程和 I/O 线程的详细而简单的描述

我对这个主题很清楚(但在技术上可能并不精确):

  • 工作线程是应该使用 CPU 进行工作的线程;
  • I/O 线程(也称为“完成端口线程”)应该使用设备驱动程序来完成它们的工作,并且基本上“什么都不做”,只监视非 CPU 操作的完成情况。

什么不清楚:

  • 尽管 ThreadPool.GetAvailableThreads 方法返回两种类型的可用线程数,但似乎没有公共 API 来安排 I/O 线程的工作。您只能在 .NET 中手动创建工作线程吗?
  • 似乎单个 I/O 线程可以监控多个 I/O 操作。这是真的吗?如果是这样,为什么 ThreadPool 默认有这么多可用的 I/O 线程?
  • 在某些文本中,我读到了该回调,它在 I/O 操作完成后由 I/O 线程执行。这是真的吗?考虑到这个回调是CPU操作,这不是工作线程的工作吗?
  • 更具体地说——ASP.NET 异步页面是否使用 I/O 线程?将 I/O 工作切换到单独的线程而不是增加工作线程的最大数量究竟有什么性能优势?是因为单个 I/O 线程确实监控多个操作吗?或者 Windows 在使用 I/O 线程时会进行更有效的上下文切换?
4

4 回答 4

83

.net/CLR 中的术语“工作线程”通常仅指除主线程之外的任何线程,它们代表生成线程的应用程序执行某些“工作”。“工作”实际上可能意味着任何事情,包括等待一些 I/O 完成。ThreadPool 保留工作线程的缓存,因为创建线程的成本很高。

.net/CLR 中的术语“I/O 线程”是指 ThreadPool 保留的线程,以便从“重叠”的 win32 调用(也称为“完成端口 I/O”)中分派 NativeOverlapped 回调。CLR 维护自己的 I/O 完成端口,并且可以将任何句柄绑定到它(通过 ThreadPool.BindHandle API)。此处示例:http: //blogs.msdn.com/junfeng/archive/2008/12/01/threadpool-bindhandle.aspx。许多 .net API 在内部使用这种机制来接收 NativeOverlapped 回调,尽管典型的 .net 开发人员不会直接使用它。

“工作线程”和“I/O 线程”之间实际上没有技术区别——它们都是普通线程。但是 CLR ThreadPool 保留每个单独的池只是为了避免对工作线程的高需求耗尽所有可用于调度本机 I/O 回调的线程的情况,这可能导致死锁。(想象一个使用所有 250 个工作线程的应用程序,每个工作线程都在等待一些 I/O 完成)。

开发人员在处理 I/O 回调时确实需要小心,以确保 I/O 线程返回到 ThreadPool——也就是说,I/O 回调代码应该做最少的工作来服务回调然后将线程的控制权返回给 CLR 线程池。如果需要更多工作,则应在工作线程上安排该工作。否则,应用程序可能会“劫持”CLR 的保留 I/O 完成线程池以用作普通工作线程,从而导致上述死锁情况。

一些很好的参考资料供进一步阅读:win32 I/O 完成端口: http: //msdn.microsoft.com/en-us/library/aa365198 (VS.85).aspx 托管线程池:http: //msdn.microsoft.com /en-us/library/0ka9477y.aspx BindHandle 示例:http: //blogs.msdn.com/junfeng/archive/2008/12/01/threadpool-bindhandle.aspx

于 2010-01-30T17:34:12.670 回答
12

我将首先描述 NT 中的程序如何使用异步 I/O。

您可能熟悉 Win32 API 函数ReadFile(作为示例),它是 Native API 函数NtReadFile的包装器。这个函数允许你用异步 I/O 做两件事:

  • 您可以创建一个事件对象并将其传递给NtReadFile。然后,当读取操作完成时,将发出此事件。
  • 您可以将异步过程调用 (APC) 函数传递给NtReadFile。本质上,这意味着当读取操作完成时,该函数将排队到启动该操作的线程,并在线程执行可警报等待时执行。

然而,当 I/O 操作完成时,还有第三种通知方式。您可以创建一个I/O 完成端口对象并将文件句柄与其关联。每当对与 I/O 完成端口关联的文件完成操作时,操作的结果(如 I/O 状态)都会排队到 I/O 完成端口。然后,您可以设置一个专用线程来从队列中删除结果并执行适当的任务,例如调用回调函数。这本质上就是“I/O 工作线程”。

一个普通的“工作线程”非常相似;它不是从队列中删除 I/O 结果,而是从队列中删除工作项。您可以将工作项 ( QueueUserWorkItem )排队并让工作线程执行它们。这可以防止您每次想要异步执行任务时都必须生成一个线程。

于 2010-01-20T09:24:55.633 回答
4

简而言之,工作线程意味着执行短时间的工作,并在完成后将自己删除。回调可用于通知父进程它已完成或传回数据。

一个 I/O 线程将连续执行相同的操作或一系列操作,直到被父进程停止。之所以这么称呼它,是因为它通常运行的设备驱动程序会持续监控设备端口。I/O 线程通常会在它希望与其他线程通信时创建事件。

所有进程都作为线程运行。您的应用程序作为线程运行。任何线程都可能产生工作线程或 I/O 线程(正如您所说的那样)。

在性能和使用的线程数量或类型之间总是存在很好的平衡。一个进程处理的太多回调或事件将严重降低其性能,因为它在处理它们时会中断其主进程循环的次数。

工作线程的示例是在用户交互后将数据添加到数据库中,或者执行长时间的数学计算或将数据写入文件。通过使用工作线程可以释放主应用程序,这对 GUI 最有用,因为它不会在执行任务时冻结。

于 2010-01-20T08:49:38.180 回答
1

比我更有技能的人会跳进来帮忙。

工作线程有很多状态,它们由处理器等进行调度,您可以控制它们所做的一切。

IO 完成端口由操作系统提供,用于涉及很少共享状态的非常具体的任务,因此使用起来更快。.Net 中的一个很好的例子是 WCF 框架。对 WCF 服务的每个“调用”实际上都是由 IO 完成端口执行的,因为它们是启动速度最快的,并且操作系统会为您处理它们。

于 2010-01-20T09:13:29.607 回答