python 多处理池并不总是使用所有工作人员

python multiprocessing Pool not always using all workers


问题:
当向 apply_async 发送 1000 个任务时,它们会在所有 48 个 CPU 上并行 运行,但有时 CPU 会越来越少 运行,直到只有一个CPU 剩下的是 运行ning,只有当最后一个完成任务时,所有 CPUs 才继续 运行ning,每个人都有一个新任务。它不需要像这样等待任何 "task batch"..

我的(简化)代码:

from multiprocessing import Pool
pool = Pool(47)
tasks = [pool.apply_async(json2features, (j,)) for j in jsons]
feats = [t.get() for t in tasks]

jsons = [...] 是一个包含大约 1000 个 JSON 的列表,这些 JSON 已经加载到内存并解析为对象。
json2features(json) 在 json 和 returns 数组上做一些 CPU 繁重的工作。
此函数可能需要 1 秒到 15 分钟才能完成 运行,因此我使用启发式 s.t 对 json 进行排序。希望最长的任务在列表中排在第一位,因此最先开始。

json2features 函数还会打印任务完成时间和耗时。所有 运行 都在具有 48 个内核的 ubuntu 服务器上,就像我上面说的,它开始很好,使用了所有 47 个内核。然后随着任务的完成,越来越少的核心 运行,起初听起来还不错,但事实并非如此,因为在最后一个核心完成后(当我看到它打印到标准输出时),所有 CPU我们将再次 运行ning 开始新的任务,这意味着它并不是真正的列表末尾。它可能会再次做同样的事情,然后在列表的实际末尾再次做同样的事情。

有时它可能只使用一个核心 5 分钟,当任务最终完成时,它会再次开始使用所有核心,用于新任务。 (所以它不会卡在一些 IPC 开销上)

没有重复的 jsons,它们之间也没有任何依赖关系(都是静态的,来自磁盘的新数据,没有引用等),json2features 调用之间也没有任何依赖关系(没有全局状态或任何东西)除了他们使用相同的终端进行打印。

我怀疑问题是直到 get 调用其结果才释放工人,所以我尝试了以下代码:

from multiprocessing import Pool
pool = Pool(47)
tasks = [pool.apply_async(print, (i,)) for i in range(1000)]
# feats = [t.get() for t in tasks]

它确实打印了所有 1000 个数字,即使没有调用 get

我现在 运行 不知道问题可能是什么。
这真的是 Pool 的正常行为吗?

非常感谢!

multiprocessing.Pool 依靠单个 os.pipe 将任务交付给工人。

通常在 Unix 上,默认管道大小范围为 4 到 64 Kb。如果您交付的 JSON 尺寸较大,您可能会在任何给定时间点堵塞管道。

这意味着,当其中一名工人忙于从管道读取大 JSON 时,所有其他工人都将挨饿。

通过 IPC 共享大数据通常是一种不好的做法,因为它会导致性能不佳。这甚至在 multiprocessing programming guidelines.

中带有下划线

Avoid shared state

As far as possible one should try to avoid shifting large amounts of data between processes.

不用在主进程中读取 JSON 文件,只需将文件名发送给工作人员,让他们打开并读取内容即可。您肯定会注意到性能的提高,因为您也在并发域中移动了 JSON 加载阶段。

请注意,结果也是如此。单个 os.pipe 也用于 return 主进程的结果。如果一个或多个工作人员堵塞了结果管道,那么您将让所有进程等待主要进程将其排出。大的结果也应该写入文件。然后,您可以在主进程上利用多线程来快速读回文件中的结果。