为什么ThreadPoolExecutor的默认max_workers是根据CPU数量来决定的?
Why is ThreadPoolExecutor's default max_workers decided based on the number of CPUs?
concurrent.futures.ThreadPoolExecutor
的文档说:
Changed in version 3.5: If max_workers is None
or not given, it will default to the number of processors on the machine, multiplied by 5
, assuming that ThreadPoolExecutor is often used to overlap I/O instead of CPU work and the number of workers should be higher than the number of workers for ProcessPoolExecutor.
我想明白为什么默认的max_workers
值取决于CPU的个数。无论我有多少个 CPU,在任何时间点都只有一个 Python 线程可以 运行。
让我们假设每个线程都是 I/O 密集型线程,它仅将 10% 的时间花在 CPU 上,90% 的时间等待 I/O。然后让我们假设我们有 2 CPUs。我们只能 运行 10 个线程来利用 100% CPU。我们不能再使用 CPU 因为在任何时间点只有一个线程 运行。即使有 4 个 CPU 也是如此。
那为什么默认的max_workers
是根据CPU的个数来决定的呢?
检查处理器的数量比检查I/O程序的绑定要容易得多,尤其是在线程池启动时,当您的程序还没有真正开始工作时。没有什么 更好 可以作为默认值的基础。
此外,添加默认设置是一个很好的 low-effort, low-discussion 更改。 (以前,没有默认值。)尝试花哨的工作会更多。
就是说,变得更漂亮可能会有回报。也许是某种根据负载调整线程数的动态系统,因此您不必在信息最少的时候决定线程数。不过,除非有人写,否则不会发生。
CPython线程实现是light-weight。它 mostly 将东西运送到 os,并考虑了 GIL(和信号处理)。增加与内核成比例的线程数通常不会奏效。由于线程由 os 管理,有许多内核,os 变得贪婪并尝试 运行 与 possible 一样多的就绪线程(如果有线程)上下文切换。他们都试图获得 GIL,但只有一个成功了。这会导致大量浪费——比假设在给定时间只有一个线程可以 运行 的线性计算更糟糕。如果您在执行程序中使用纯 CPU-bound 线程,则没有理由因此将其 link 到核心。但是我们不应该剥夺真正想要 CPU 能力并且同意 GIL 版本来利用核心的用户。所以可以说,在这种情况下,默认值应该 linked 到核心数量 - 如果你假设 most 人 运行ning Python 知道它们是什么正在做。
现在如果执行器中的线程是 I/O-bound,那么您正确地提到最大容量是 1/p,其中 p 是每个线程需要的 CPU 的分数。为了确定默认值,不可能os事先知道 p 是什么。默认的最小值 0.2(最少 5 个线程)看起来还不错。但通常我的猜测是这个 p 会低得多,所以限制因素可能永远不会是 CPU(但如果是,我们又会遇到上面的 CPU 多核抖动问题) .因此 link 核数可能最终不会变得不安全(除非线程处理繁重或者你有太多核!)。
concurrent.futures.ThreadPoolExecutor
的文档说:
Changed in version 3.5: If max_workers is
None
or not given, it will default to the number of processors on the machine, multiplied by5
, assuming that ThreadPoolExecutor is often used to overlap I/O instead of CPU work and the number of workers should be higher than the number of workers for ProcessPoolExecutor.
我想明白为什么默认的max_workers
值取决于CPU的个数。无论我有多少个 CPU,在任何时间点都只有一个 Python 线程可以 运行。
让我们假设每个线程都是 I/O 密集型线程,它仅将 10% 的时间花在 CPU 上,90% 的时间等待 I/O。然后让我们假设我们有 2 CPUs。我们只能 运行 10 个线程来利用 100% CPU。我们不能再使用 CPU 因为在任何时间点只有一个线程 运行。即使有 4 个 CPU 也是如此。
那为什么默认的max_workers
是根据CPU的个数来决定的呢?
检查处理器的数量比检查I/O程序的绑定要容易得多,尤其是在线程池启动时,当您的程序还没有真正开始工作时。没有什么 更好 可以作为默认值的基础。
此外,添加默认设置是一个很好的 low-effort, low-discussion 更改。 (以前,没有默认值。)尝试花哨的工作会更多。
就是说,变得更漂亮可能会有回报。也许是某种根据负载调整线程数的动态系统,因此您不必在信息最少的时候决定线程数。不过,除非有人写,否则不会发生。
CPython线程实现是light-weight。它 mostly 将东西运送到 os,并考虑了 GIL(和信号处理)。增加与内核成比例的线程数通常不会奏效。由于线程由 os 管理,有许多内核,os 变得贪婪并尝试 运行 与 possible 一样多的就绪线程(如果有线程)上下文切换。他们都试图获得 GIL,但只有一个成功了。这会导致大量浪费——比假设在给定时间只有一个线程可以 运行 的线性计算更糟糕。如果您在执行程序中使用纯 CPU-bound 线程,则没有理由因此将其 link 到核心。但是我们不应该剥夺真正想要 CPU 能力并且同意 GIL 版本来利用核心的用户。所以可以说,在这种情况下,默认值应该 linked 到核心数量 - 如果你假设 most 人 运行ning Python 知道它们是什么正在做。
现在如果执行器中的线程是 I/O-bound,那么您正确地提到最大容量是 1/p,其中 p 是每个线程需要的 CPU 的分数。为了确定默认值,不可能os事先知道 p 是什么。默认的最小值 0.2(最少 5 个线程)看起来还不错。但通常我的猜测是这个 p 会低得多,所以限制因素可能永远不会是 CPU(但如果是,我们又会遇到上面的 CPU 多核抖动问题) .因此 link 核数可能最终不会变得不安全(除非线程处理繁重或者你有太多核!)。