ThreadPool 线程正在执行 I/O 操作 - 线程可以在等待时重用吗?
ThreadPool thread doing I/O operation - can thread be reused while waiting?
我已经阅读了一些关于 async/await vs ThreadPool vs Threads 的文章,我不得不承认,我对细节并不完全清楚。有一个具体问题我认为我有答案,但我不能肯定地说。
我也知道关于 SO 和其他地方的许多问题,这些问题正在讨论和解释中。我在这里阅读了很多关于 SO 的文章,但我还没有找到明确的答案,或者至少没有我想要的那么清楚。
我收集到的:
- 在 I/O 操作中使用 async/await,将使该线程可用于其他事情,而 I/O 操作在其他地方继续
- 对于服务器应用程序,这意味着更高的吞吐量等
然而,一位同事说了这样的话:
- 使用线程池执行相同的 I/O 操作的行为几乎与 async/await
相同
- 当 ThreadPool 线程 "hands over" 对 OS 进行 I/O 操作时,它可能会等待答案,但这无关紧要,因为没有在 CPU 上完成的工作(线程正在等待),因此 ThreadPool/OS/framework 可以使用或生成另一个线程来完成其他工作。
- 由于线程池有大约 32000+ 个线程的限制,所以这无关紧要,因为它可以使用更多线程。一个线程的开销很小,几个字节的内存
那么,我要问的是:
- 线程池中的线程是否在 I/O 操作中阻塞?
- 它是否重要,因为如果其他工作需要,OS/framework/Threadpool 可以使用池中的另一个线程?
- 底线:I/O 使用 async/await 或 ThreadPool 的操作;这对效率和吞吐量重要吗?
请注意,我不是在讨论客户端,只是从服务器的角度。
如果我错过了这个问题的确切答案,请提前道歉,我已经看过了 =)
真正的问题不是 "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" —— 如果是,那么您做错了。
我已经阅读了一些关于 async/await vs ThreadPool vs Threads 的文章,我不得不承认,我对细节并不完全清楚。有一个具体问题我认为我有答案,但我不能肯定地说。
我也知道关于 SO 和其他地方的许多问题,这些问题正在讨论和解释中。我在这里阅读了很多关于 SO 的文章,但我还没有找到明确的答案,或者至少没有我想要的那么清楚。
我收集到的:
- 在 I/O 操作中使用 async/await,将使该线程可用于其他事情,而 I/O 操作在其他地方继续
- 对于服务器应用程序,这意味着更高的吞吐量等
然而,一位同事说了这样的话:
- 使用线程池执行相同的 I/O 操作的行为几乎与 async/await 相同
- 当 ThreadPool 线程 "hands over" 对 OS 进行 I/O 操作时,它可能会等待答案,但这无关紧要,因为没有在 CPU 上完成的工作(线程正在等待),因此 ThreadPool/OS/framework 可以使用或生成另一个线程来完成其他工作。
- 由于线程池有大约 32000+ 个线程的限制,所以这无关紧要,因为它可以使用更多线程。一个线程的开销很小,几个字节的内存
那么,我要问的是:
- 线程池中的线程是否在 I/O 操作中阻塞?
- 它是否重要,因为如果其他工作需要,OS/framework/Threadpool 可以使用池中的另一个线程?
- 底线:I/O 使用 async/await 或 ThreadPool 的操作;这对效率和吞吐量重要吗?
请注意,我不是在讨论客户端,只是从服务器的角度。
如果我错过了这个问题的确切答案,请提前道歉,我已经看过了 =)
真正的问题不是 "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" —— 如果是,那么您做错了。