为什么ThreadPoolExecutor的默认max_workers是根据CPU数量决定的?

时间:2019-05-18 03:12:47

标签: python multithreading performance cpu gil

concurrent.futures.ThreadPoolExecutor的文档说:

  

在版本3.5中进行了更改:如果 max_workers None或未指定,它将默认为计算机上的处理器数量乘以{{ 1}},假设ThreadPoolExecutor通常用于重叠I / O而不是CPU工作,并且工作程序的数量应大于ProcessPoolExecutor的工作程序的数量。

我想了解为什么默认5值取决于CPU数量。不管我有多少CPU,任何时候都只能运行一个Python线程。

让我们假设每个线程都是I / O密集型的,它仅在CPU中花费10%的时间,在I / O中花费90%的时间。然后让我们假设我们有2个CPU。我们只能运行10个线程来利用100%的CPU。我们无法再利用更多的CPU,因为在任何时间点仅运行一个线程。即使有4个CPU,也是如此。

那为什么要根据CPU的数量来确定默认的max_workers

2 个答案:

答案 0 :(得分:2)

CPython线程实现是轻量级的。它主要将这些东西交付给os,并考虑了GIL(和信号处理)。通常无法解决与内核成比例的线程数量增加的问题。由于线程是由具有许多内核的os管理的,因此如果存在线程上下文切换,则os会变得贪婪并尝试运行尽可能多的就绪线程。他们所有人都试图获得GIL,只有一个成功。这导致了很多浪费-比假设在给定时间只能运行一个线程的线性计算更糟糕。如果在执行程序中使用纯CPU绑定线程,则没有理由将其链接到内核。但是,我们不应该剥夺那些真正想要CPU功能并且可以使用GIL版本使用这些内核的用户。因此可以说,在这种情况下,默认值应该与内核数相关联-如果您假设大多数运行Python的人都知道他们在做什么。

现在,如果执行程序中的线程是I / O绑定的,那么您正确地提到了最大容量为1 / p,其中p是每个线程所需的CPU分数。为了确定默认值,不可能事先知道p是什么。默认的最小值为0.2(最少5个线程)看起来还不错。但是通常我的猜测是该p会低得多,因此限制因素可能永远不会是CPU(但是如果是的话,我们同样会陷入上述多核的CPU抖动问题)。因此,链接到多个内核可能不会最终变得不安全(除非线程进行大量处理或内核过多!)。

答案 1 :(得分:0)

检查处理器数量比检查程序的I / O绑定要容易得多,尤其是在线程池启动时(当程序尚未真正开始工作时)。确实没有更好作为默认设置的基础。

此外,添加默认设置是一个不错的low-effortlow-discussion更改。 (以前没有默认设置。)想要花哨的东西本来可以做更多的工作。

也就是说,发烧友可能会有所回报。也许某种动态系统会根据负载调整线程数,所以您不必在信息最少的时候决定线程数。除非有人写,否则它不会发生。