多处理模块中的 ThreadPool 与 Pool 有啥区别?

Posted

技术标签:

【中文标题】多处理模块中的 ThreadPool 与 Pool 有啥区别?【英文标题】:What's the difference between ThreadPool vs Pool in the multiprocessing module?多处理模块中的 ThreadPool 与 Pool 有什么区别? 【发布时间】:2018-02-13 05:13:03 【问题描述】:

multiprocessing 模块中的ThreadPoolPool 有什么区别。当我尝试我的代码时,这是我看到的主要区别:

from multiprocessing import Pool
import os, time

print("hi outside of main()")

def hello(x):
    print("inside hello()")
    print("Proccess id: ", os.getpid())
    time.sleep(3)
    return x*x

if __name__ == "__main__":
    p = Pool(5)
    pool_output = p.map(hello, range(3))

    print(pool_output)

我看到以下输出:

hi outside of main()
hi outside of main()
hi outside of main()
hi outside of main()
hi outside of main()
hi outside of main()
inside hello()
Proccess id:  13268
inside hello()
Proccess id:  11104
inside hello()
Proccess id:  13064
[0, 1, 4]

使用“线程池”:

from multiprocessing.pool import ThreadPool
import os, time

print("hi outside of main()")

def hello(x):
    print("inside hello()")
    print("Proccess id: ", os.getpid())
    time.sleep(3)
    return x*x

if __name__ == "__main__":
    p = ThreadPool(5)
    pool_output = p.map(hello, range(3))

    print(pool_output)

我看到以下输出:

hi outside of main()
inside hello()
inside hello()
Proccess id:  15204
Proccess id:  15204
inside hello()
Proccess id:  15204
[0, 1, 4]

我的问题是:

为什么每次都在Pool中运行“外部__main__()”?

multiprocessing.pool.ThreadPool 不会产生新进程?它只是创建新线程?

如果是这样,使用multiprocessing.pool.ThreadPool 与仅使用threading 模块有什么区别?

我在任何地方都没有看到任何ThreadPool 的官方文档,有人可以帮我看看在哪里可以找到它吗?

【问题讨论】:

据我所知,由于Python中的GIL,Python的多线程看起来像多线程,但不是真的。如果你想通过 python 来利用你的多核,你需要使用多处理。在现代计算机中,创建进程和创建线程的成本几乎相同。 创建线程的成本可能与创建进程的成本相似,但线程之间的通信与进程之间的通信成本却大不相同(除非您使用共享内存)。此外,您对 GIL 的评论只是部分正确:它是在 I/O 操作期间发布的,甚至在 CPU 绑定操作期间也由某些库(例如 numpy)发布。尽管如此,GIL 最终还是在 Python 中使用单独进程的原因。 @Yves 通过使用fork,在 *nix 上可能是这样,但在 Windows 上却不是这样,并且没有考虑到进程之间通信的额外开销、限制和复杂性,因为与线程相反(在所有平台上)。 要回答threadingThreadPool 的问题,threading 没有简单的直接方法来获取工作函数的返回值。而在ThreadPool 中,您可以轻松获取工作函数的返回值。 【参考方案1】:

multiprocessing.pool.ThreadPool 的行为与 multiprocessing.Pool 相同,唯一的区别是使用线程而不是进程来运行工作逻辑。

你看到的原因

hi outside of main()

使用multiprocessing.Pool 多次打印是因为池将spawn 5 个独立进程。每个进程将初始化自己的 Python 解释器并加载模块,从而再次执行*** print

请注意,只有在使用 spawn 进程创建方法时才会发生这种情况(仅适用于 Windows 的方法)。如果您使用fork 之一(Unix),您将看到消息只打印一次,就像线程一样。

multiprocessing.pool.ThreadPool 没有记录,因为它的实现从未完成。它缺乏测试和文档。你可以在source code看到它的实现。

我相信下一个自然问题是:何时使用基于线程的池以及何时使用基于进程的池?

经验法则是:

IO 绑定作业 -> multiprocessing.pool.ThreadPool CPU 绑定作业 -> multiprocessing.Pool 混合作业 -> 取决于工作负载,由于进程隔离带来的优势,我通常更喜欢multiprocessing.Pool

在 Python 3 上,您可能想看看 concurrent.future.Executor 池实现。

【讨论】:

感谢您的回答。我只想理解这句话:请注意,只有在使用 spawn 进程创建方法时才会发生这种情况(仅在 Windows 上可用的方法)。如果您使用 fork one (Unix),您将看到只打印一次线程的消息。我假设,当我调用“map()”或“Pool()”时,“spawn”和“fork”是隐含的?还是这是我可以控制的? 解释在我上面提到spawn启动方法时给你的链接中。您可以控制它,但启动方法的可用性取决于操作系统平台。我假设您使用 Windows 作为默认启动策略是 spawn 之一。如果是这样,则几乎没有什么可做的,因为 Windows 仅支持 spawn 关于 ThreadPool 未完成实现的评论在 2019 年使用 Python 3.7 是否仍然有效? 是的。正如您从链接源和缺乏文档中看到的那样。 @MrR,这绝对是合理和真实的,但这实际上并没有解决为什么 IO绑定作业应该更喜欢线程池而不是池(进程);不过,我想这可以简单地通过常识来回答,即分叉整个子流程所需的时间以及由于无法共享相同资源而导致的额外开销。

以上是关于多处理模块中的 ThreadPool 与 Pool 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

C#多线程实现方法——线程池(Thread Pool)

通过threadpool初试多线程

C 语言编程 — 线程池设计与实现

python3 线程池-threadpool模块与concurrent.futures模块

boost::threadpool::pool vs.boost::thread_group

《Elasticsearch 源码解析与优化实战》第16章:ThreadPool模块分析