执行I / O操作的ThreadPool线程-等待时可以重用线程吗?

Ted*_*Ted 2 .net c# multithreading threadpool async-await

我已经阅读了一些有关异步/等待vs线程池vs线程的知识,我不得不承认,我不清楚细节。我我有一个具体的问题的答案,但是我不能肯定地说。

我也知道关于SO和其他地方的许多问题,这些问题正在讨论和解释中。我在SO上读到了不少文章,但没有找到明确的答案,或者至少没有我想要的清晰。

我收集了什么:

  • 在I / O操作中使用async / await,将使该线程可用于其他用途,而I / O操作将在其他地方继续进行
  • 对于服务器应用程序,这意味着更高的吞吐量等

但是,一位同事说了这样的话:

  • 使用ThreadPool执行相同的I / O操作的行为与async / await几乎相同
  • 当ThreadPool线程将I / O操作“移交给”操作系统时,它可能会等待答案,但这无关紧要,因为在CPU上没有任何工作(线程正在等待),因此,ThreadPool / OS /框架可以使用或生成另一个线程来执行其他一些工作。
  • 由于线程池最多只能有32000个线程,因此没关系,因为它可以使用更多线程。线程的开销很小,只有几个字节的内存

所以,我要问的是:

  • 来自ThreadPool的线程是否在I / O操作中阻塞?
  • 是否有关系,因为OS / framework / Threadpool可以在需要其他工作时仅使用池中的另一个线程?
  • 底线:使用async / await或ThreadPool的I / O操作;对效率和生产量有影响吗?

请注意,我不是从服务器的角度讨论客户端的事情。

预先抱歉,如果我错过了确切的答案,我已经看过=)

Jer*_*ert 5

真正的问题不是“线程池与async/ await”,它的同步I / O和异步I / O。[1]

在Windows上,线程是相对昂贵的对象-线程具有大量与之相关的操作系统记账,更不用说1 MB的预分配堆栈空间。这对系统甚至支持多少线程设置了一个较低的上限,即使您没有达到该上限,在所有这些线程之间进行上下文切换也不便宜。“ 32,000个线程”的限制是一个严格的理论限制,并且对您可以拥有多少个线程并且仍然保持响应状态非常乐观![2]

输入异步I / O,将其优化为仅使用所需的多个线程(通常是系统中物理处理器内核数量的保守倍数),理想情况下甚至不要创建超出初始批处理的新线程。这些线程专用于通过从队列(称为完成端口)中删除它们来处理已完成的I / O操作。在进行异步操作时,根本没有线程专用于该线程,甚至没有将其作为等待列表中的项(Stephen Cleary在其博客文章中对此进行了详细介绍)。无需多大的想象力就可以考虑更有效的方法:

  • 每一个等待特定操作的数千个线程必须被唤醒,并根据完成的操作切换到(以及在之间切换);要么
  • 几十个线程(如果有的话),每个线程都可以处理任何已完成的操作,因此只需要运行尽可能多的线程即可做出响应。

事实证明,后者的规模要比前者好得多。即使在使用线程池减少创建新线程的情况下,天真服务器代码中常见的“每个请求线程”模型也会很快显示其局限性。请注意,这早在async/ await曾经是一个问题之前就已成为问题,因此Windows使用的解决方案也是如此。async/ await只是一种编写代码以使用现有机制的新方法。

您可能一次只注意到几个飞行请求,便会发现有所不同吗?不会。但是由于async/ await本质上允许您编写看似同步但具有“免费”的异步I / O可伸缩性的代码,为什么您选择使用支持排队到线程池的同步I / O?


[1]事实证明,斯蒂芬·克雷里(Stephen Cleary 早在几年前就已经写了这个答案大部分内容。我建议您也阅读。

[2]这是Mark Russinovich 的一篇较旧的文章,他实际上试图从系统中挤出尽可能多的线程-只是为了娱乐和牟利。在所有资源用尽之前,他“只能”在64位计算机上达到55K,这是通过调整默认堆栈大小,而不进行任何实际有用的工作。在现代系统上,您可能会得到更多,但真正的问题不应是“我可以拥有多少个线程”-如果是,那您做错了。

  • “真正的问题不是‘线程池与异步/等待’”——确切地说。正如我在[异步 ASP.NET 文章](https://msdn.microsoft.com/en-us/magazine/dn802603.aspx) 中所说,异步允许您的应用程序最大限度地利用线程池。这不是“非此即彼”的讨论;而是“非此即彼”的讨论。这是“两者/和”。 (2认同)