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

ThreadPool thread doing I/O operation - can thread be reused while waiting?

我已经阅读了一些关于 async/await vs ThreadPool vs Threads 的文章,我不得不承认,我对细节并不完全清楚。有一个具体问题我认为我有答案,但我不能肯定地说。

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

我收集到的:

然而,一位同事说了这样的话:

那么,我要问的是:

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

如果我错过了这个问题的确切答案,请提前道歉,我已经看过了 =)

真正的问题不是 "thread pool vs. async/await",而是同步 I/O 与异步 I/O。 [1]

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

进入异步I/O,它被优化为只使用必要数量的线程(通常是系统中物理处理器核心数量的一些保守倍数),理想情况下甚至不创建超出初始线程的新线程批。这些线程专用于处理已完成的 I/O 操作,方法是将它们从队列(称为完成端口)中移除。当异步操作正在进行时,根本没有线程专用于它,甚至没有作为等待列表中的项目(Stephen Cleary 有一个很好的 blog post 关于它更详细地解释了这一点)。不需要太多的想象力,想想怎样做效率更高:

  • 几千个单独的线程,每个线程都在等待一个特定的操作,这些线程必须被唤醒并根据完成的操作切换到(和切换到);或
  • 几十个线程(如果有的话),每个线程都可以处理任何已完成的操作,因此只需要 运行 响应所需的线程数。

事实证明,后者的规模比前者好得多;在原始服务器代码中常见的 "thread per request" 模型很快就会显示出其局限性,即使您使用线程池来减少新线程的创建也是如此。请注意,这在 async/await 出现之前就已经是一个问题了,解决方案 Windows 也是如此; async/await只是利用现有机制编写代码的一种新方式。

您是否可能注意到一次只有几个请求在飞行中的差异?不,但是既然 async/await 本质上允许您编写看起来同步但具有异步 I/O "for free" 可扩展性的代码,为什么您 选择使用支持同步的 I/O 排队到线程池?


[1] 结果发现 Stephen Cleary 已经 wrote most of what's in this answer 几年前了。我建议您也阅读它。

[2] 这是 Mark Russinovich 的 older post,他实际上试图从系统中挤出尽可能多的线程——只是为了乐趣和利润。他 "only" 在所有资源都用完之前在 64 位机器上达到 55K,这是在调整默认堆栈大小的情况下进行的,并且没有做任何实际有用的工作。在现代系统中,您可能会得到更多,但真正的问题不应该是 "how many threads can I have" —— 如果是,那么您做错了。