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

时间:2019-06-03 10:03:44

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

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

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

我收集了什么

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

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

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

所以,我要问的是:

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

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

如果对这个问题没有确切的答案,请提前抱歉,我已经看过=)

1 个答案:

答案 0 :(得分:5)

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

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

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

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

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

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


[1]证明斯蒂芬·克拉里(Stephen Cleary)几年前wrote most of what's in this answer。我也建议您阅读。

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