使用多进程而不是线程增加了一些实质性的开销。大多数情况下,它会增加内存占用,
因为每个进程都有自己独立的内存上下文。这意味着允许未绑定数量的子进程比在多线程
应用程序中更有问题。
与“使用线程池”中的描述的线程方式类似,构建一个进程池,这是在依赖多进程以
获得更好资源利用率的应用程序中控制资源使用的最佳模式。
multiprocessing 模块最好的一点是它提供了一个即用型的 Pool 类,可以处理管理
多个工作进程的所有复杂性。这个池实现大大减少了所需的样板数量和与双向通信相关的问题
数量。你也不需要手动使用 join()方法,因为 Pool 可以用作上下文管理器(使用 with 语
句)。以下是我们以前的一个线程示例,使用 multiprocessing 模块中的 Pool 类进行改写:
from multiprocessing import Pool
from gmaps import Geocoding
api = Geocoding()
PLACES = (
‘Reykjavik’, ‘Vien’, ‘Zadar’, ‘Venice’,
‘Wrocław’, ‘Bolognia’, ‘Berlin’, ‘Słubice’,
‘New York’, ‘Dehli’,
)
POOL_SIZE = 4
def fetch_place(place):
return api.geocode(place)[0]
def present_result(geocoded):
print(“{:>25s}, {:6.2f}, {:6.2f}”.format(
geocoded[‘formatted_address’],
geocoded[‘geometry’][‘location’][‘lat’],
geocoded[‘geometry’][‘location’][‘lng’],
))
def main():
with Pool(POOL_SIZE) as pool:
results = pool.map(fetch_place, PLACES)
for result in results:
present_result(result)
if name == “main”:
main()
正如你所看到的,代码现在要短得多。这意味着如果出现问题,它现在更容易维护和
调试。实际上,现在只有两行代码明确使用多进程。相对于我们必须从头开始构建进程池
的情况,这是一个很大的改进。现在我们甚至不需要关心通信通道,因为它们是隐含地在
Pool 实现中创建的。
使用 multiprocessing.dummy 作为 multithreading 接口
来自 multiprocessing 模块的高级抽象,例如 Pool 类,与在 threading 模块中
提供的简单工具相比具有很大的优点。但不是,这并不意味着多进程总是是比多线程更好
的方法。有很多用例,其中线程可能是一个比进程更好的解决方案。这在需要低延迟和/或
高资源效率的情况下尤其如此。
但这并不意味着你需要牺牲 multiprocessing 模块中的所有有用的抽象,只要你想
使用线程而不是进程。有 multiprocessing.dummy 模块,它复制 multiprocessing
API,但使用多个线程,而不是派生/产生新进程。
这可以减少代码中的样板代码数量,并且还可以创建更多可插入的接口。例如,让我
们再看看我们的之前的例子的 main()。如果我们想让用户控制他想要使用哪个处理后端
(进程或线程),我们可以简单地替换 Pool 类,如下所示:
from multiprocessing import Pool as ProcessPool
from multiprocessing.dummy import Pool as ThreadPool
def main(use_threads=False):
if use_threads:
pool_cls = ThreadPool
else:
pool_cls = ProcessPool
with pool_cls(POOL_SIZE) as pool:
results = pool.map(fetch_place, PLACES)
for result in results:
present_result(result)
Python 高手编程系列三百零四:使用进程池
最新推荐文章于 2024-08-29 14:47:26 发布