关于Python中并行处理的快速问题。假设我有一个很大的共享数据结构,并希望并行应用许多函数。这些函数只在数据结构上读取,但在结果对象中执行变异:
def compute_heavy_task(self):
big_shared_object = self.big_shared_object
result_refs = self.result_refs
for ref in result_refs:
some_expensive_task(ref, big_shared_object)
我如何并行执行这些操作,一次说5个,或者一次10个。一次处理器的数量如何?
答案 0 :(得分:4)
你不能用Python中的线程来做这件事(至少不是你可能正在使用的CPython实现)。 Global Interpreter Lock意味着,您只需要获得90%的成功,而不是8个核心中的近800%效率。
但您可以使用单独的流程执行此操作。标准库中内置了两个选项:concurrent.futures
和multiprocessing
。一般来说,futures
在简单的情况下更简单,并且通常更容易编写; multiprocessing
一般来说更灵活,更强大。 futures
也只有Python 3.2或更高版本,但有a backport for 2.5-3.1 at PyPI。
您希望multiprocessing
具有灵活性的情况之一是您拥有大型共享数据结构。有关详细信息,请参阅Sharing state between processes以及上方,下方和相关链接的部分。
如果您的数据结构非常简单,就像一大堆整数,这很简单:
class MyClass(object):
def __init__(self, giant_iterator_of_ints):
self.big_shared_object = multiprocessing.Array('i', giant_iterator_of_ints)
def compute_heavy_task(self):
lock = multiprocessing.Lock()
def subtask(my_range):
return some_expensive_task(self.big_shared_object, lock, my_range)
pool = multiprocessing.pool.Pool(5)
my_ranges = split_into_chunks_appropriately(len(self.big_shared_object)
results = pool.map_async(subtask, my_ranges)
pool.close()
pool.join()
请注意,some_expensive_task
函数现在需要一个锁定对象 - 它必须确保获取对共享对象的每次访问的锁定(或者更常见的是,由一个或多个组成的每个“事务”)访问)。锁定纪律可能很棘手,但如果您想使用直接数据共享,则无法绕过它。
另请注意,它需要my_range
。如果你只是在同一个对象上调用相同的函数5次,它会做同样的事情5次,这可能不是很有用。并行化事物的一种常用方法是为每个任务提供整个数据集的子范围。 (除了通常很容易描述之外,如果你对此很谨慎,使用正确的算法,你甚至可以避免这种锁定。)
如果你想要将一堆不同的函数映射到同一个数据集,你显然需要一些函数集合来处理,而不是仅仅使用{{ 1}}反复。然后,您可以例如迭代这些函数,在每个函数上调用some_expensive_task
。但是你也可以把它转过来:编写一个应用程序函数,作为数据的闭包,它需要一个函数并将它应用于数据。然后,只对功能集合起作用的apply_async
。
我还假设您的数据结构是您可以使用map
定义的。如果没有,您将不得不以C风格设计数据结构,将其实现为multiprocessing.Array
ctypes
Array
,反之亦然,然后使用{{ 1}}东西。
我还将结果对象移动到刚刚传回的结果中。如果它们也很庞大并需要共享,请使用相同的技巧使它们共享。
在进一步讨论之前,您应该问问自己是否确实需要共享数据。以这种方式做事,你将花费80%的调试,性能调整等时间添加和删除锁,使它们或多或少粒度等等。如果你可以通过传递不可变数据结构,或者处理文件,数据库或几乎任何其他替代方案,80%可以用于代码的其余部分。