作为并发使用的新手,我对何时使用不同的 python 并发库感到困惑。据我了解,多处理、多线程和异步编程是并发的一部分,而多处理是称为并行性的并发子集的一部分。
我在网上搜索了 python 中实现并发的不同方法,我发现了多处理库、concurrenct.futures 的 ProcessPoolExecutor() 和 ThreadPoolExecutor() 以及 asyncio。让我困惑的是这些库之间的区别。特别是多处理库的作用,因为它有像 pool.apply_async 这样的方法,它也能完成 asyncio 的工作吗?如果是这样,为什么它被称为多处理,因为它是与 asyncio 实现并发的不同方法(多进程与协作多任务)?
我们来看看标准库提供的相关并发相关模块:
threading
:操作系统级线程的接口。请注意,CPU 密集型工作主要由 GIL 进行序列化,因此不要指望线程能够加快计算速度。当您需要并行调用阻塞 API 以及需要精确控制线程创建时,请使用它。避免创建太多线程(例如数千个),因为它们不是免费的。如果可能,不要自己创建线程,而是使用 concurrent.futures
。
multiprocessing
:使用与threading
类似的API生成多个Python进程的接口。多个进程并行工作,因此您实际上可以使用此方法加快计算速度。缺点是,如果不使用特定于多处理的工具,则无法共享内存中的数据结构。
concurrent.futures
:threading
和multiprocessing
的现代接口,它提供了调用执行器的方便的线程/进程池。池的主要入口点是 submit
方法,它返回一个 handle,您可以测试完成情况或等待其结果。获取结果会为您提供所提交函数的返回值,并正确传播引发的异常(如果有),这对于 threading
来说会很乏味。 concurrent.futures 应该是考虑基于线程或进程的并行性时的首选工具。
asyncio
:虽然前面的选项是“异步”的,因为它们提供了非阻塞API(这就是像apply_async
这样的方法所指的),但它们仍然依赖于线程/进程池来发挥他们的魔力,并且实际上不能并行做比池中工人更多的事情。 Asyncio 是不同的:它使用单线程执行和全面的异步系统调用。它根本没有阻塞调用,唯一的阻塞部分是 asyncio.run()
入口点。异步代码通常使用协程编写,协程使用 await
暂停,直到发生有趣的事情。 (挂起与阻塞不同,它允许事件循环线程在等待时继续执行其他操作。)与基于线程的解决方案相比,它具有许多优点,例如能够生成数千个廉价的“任务”,而无需等待。使系统陷入困境,并且能够取消任务或轻松地同时等待多个任务。 Asyncio 应该成为服务器和连接到多个服务器的客户端的首选工具。
在 asyncio 和多线程/多处理之间进行选择时,请考虑“线程用于并行工作,异步用于并行等待”这句格言。
另请注意,asyncio 可以在 concurrent.futures
提供的线程或进程池中等待函数
executed,因此它可以充当所有这些不同模型之间的粘合剂。这就是为什么 asyncio 经常被用来构建新的图书馆基础设施的部分原因。