Python 将难堪的可并联发电机并联的简单方法
我有一个发电机(或者,一个发电机列表)。让我们称他们为氏族 gens中的每个生成器都是一个复杂的函数,返回复杂过程的下一个值。幸运的是,它们彼此独立 我想为Python 将难堪的可并联发电机并联的简单方法,python,parallel-processing,generator,Python,Parallel Processing,Generator,我有一个发电机(或者,一个发电机列表)。让我们称他们为氏族 gens中的每个生成器都是一个复杂的函数,返回复杂过程的下一个值。幸运的是,它们彼此独立 我想为gens中的每个元素gen调用gen.\uuu next\uuu(),并在列表中返回结果值。然而,酸洗发生器的多重处理并不令人满意 在Python中是否有一种快速、简单的方法来实现这一点?我希望长度为m的gens在我的机器上本地映射到n个核,其中n可以大于或小于m。每台发电机应在单独的铁芯上运行 如果这是可能的,有人能提供一个简单的例子吗?如
gens
中的每个元素gen
调用gen.\uuu next\uuu()
,并在列表中返回结果值。然而,酸洗发生器的多重处理并不令人满意
在Python中是否有一种快速、简单的方法来实现这一点?我希望长度为m的gens
在我的机器上本地映射到n个核,其中n可以大于或小于m。每台发电机应在单独的铁芯上运行
如果这是可能的,有人能提供一个简单的例子吗?如果您的子任务是真正并行的(不依赖任何共享状态),您可以使用
多进程.Pool()来实现这一点
看看
这要求您使pool.map()的参数可序列化。您不能将生成器传递给工作程序,但可以通过在目标函数中定义生成器,并将初始化参数传递给多处理库来实现类似的功能:
import multiprocessing as mp
import time
def worker(value):
# The generator is defined inside the multiprocessed function
def gen():
for k in range(value):
time.sleep(1) # Simulate long running task
yield k
# Execute the generator
for x in gen():
print(x)
# Do something with x?
pass
pool = mp.Pool()
pool.map(worker, [2, 5, 2])
pool.join() # Wait for all the work to be finished.
pool.close() # Clean up system resources
输出将是:
0
0
0
1
1
1
2
3
4
请注意,此解决方案只有在您构建生成器,然后只使用它们一次时才真正起作用,因为它们的最终状态在辅助函数结束时丢失
请记住,由于进程间通信的限制,任何时候想要使用多处理时,都必须使用for serializable objects;这常常被证明是有限的
如果您的进程不受CPU限制,而是受I/O限制(磁盘访问、网络访问等),那么使用线程就会容易得多。您不能对生成器进行pickle处理。阅读更多关于它的信息
有一篇博文对此做了更详细的解释。引用其中的一段话:
让我们暂时忽略这个问题,看看我们需要做些什么来处理发电机。由于生成器本质上是一个增强的函数,我们需要保存它的字节码(不能保证在Python版本之间向后兼容)和它的框架(保存生成器的状态,如局部变量、闭包和指令指针)。后者的实现相当麻烦,因为它基本上需要使整个解释器可选择。因此,对酸洗发电机的任何支持都需要对CPython的核心进行大量更改
现在,如果生成器的局部变量中出现pickle不支持的对象(例如,文件句柄、套接字、数据库连接等),那么该生成器无法自动pickle,无论我们可能实现的生成器是否支持pickle。因此,在这种情况下,您仍然需要提供定制的getstate和setstate方法。此问题使得发电机的酸洗支持相当有限
他还提出了一个解决方案,使用简单的迭代器
这个问题的最佳解决方案是将生成器重写为简单的迭代器(即,使用\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
方法的迭代器)。迭代器很容易且高效地对空间进行pickle,因为它们的状态是显式的。但是,您仍然需要显式地处理表示某些外部状态的对象;你不能回避这个问题
另一个提议(我没有试过)表明了这一点
将发电机转换为一个类别,其中发电机代码是\uuuu iter\uuuu
方法
将\uuu getstate\uuuu
和\uuu setstate\uuuu
方法添加到类中,以处理拾取。请记住,不能对文件对象进行pickle。因此,必要时,\uuuuuu设置状态
必须重新打开文件
您不需要对生成器进行pickle,只需将生成器的索引发送到处理池
M = len(gens)
N = multiprocessing.cpu_count()
def proc(gen_idx):
return [r for r in gens[gen_idx]()]
if __name__ == "__main__":
with multiprocessing.Pool(N) as p:
for r in p.imap_unordered(proc, range(M)):
print(r)
请注意,直到在处理函数中,我才调用/初始化生成器
使用imap\u unordered
将允许您在每个生成器完成时处理结果。这很容易实现,只是不要随意阻塞线程,只需不断循环状态并在完成时连接它们。这个模板应该足够好,可以给出一个想法,self.done
alwais需要在线程完成时设置为最后一个,在线程重用时设置为最后一个
import threading as th
import random
import time
class Gen_thread(th.Thread):
def is_done(self):
return self.done
def get_result(self):
return self.work_result
def __init__(self, *args, **kwargs):
self.g_id = kwargs['id']
self.kwargs = kwargs
self.args = args
self.work_result = None
self.done = False
th.Thread.__init__(self)
def run(self):
# time.sleep(*self.args) to pass variables
time.sleep(random.randint(1, 4))
self.work_result = 'Thread {0} done'.format(self.g_id + 1)
self.done = True
class Gens(object):
def __init__(self, n):
self.n_needed = 0
self.n_done = 0
self.n_loop = n
self.workers_tmp = None
self.workers = []
def __iter__(self):
return self
def __next__(self):
if self.n_needed == 0:
for w in range(self.n_loop):
self.workers.append(Gen_thread(id=w))
self.workers[w].start()
self.n_needed += 1
while self.n_done != self.n_needed:
for w in range(self.n_loop):
if self.workers[w].is_done():
self.workers[w].join()
self.workers_tmp = self.workers[w].get_result()
self.workers.pop(w)
self.n_loop -= 1
self.n_done += 1
return self.workers_tmp
raise StopIteration()
if __name__ == '__main__':
for gen in Gens(4):
print(gen)
这回答了你的问题吗?它可能在正确的轨道上,但我正在寻找一个最小的解决方案。IMO,至少有几个因素——文件i/o和未运行的示例——阻止它成为真正易于使用和易于理解的问答。但我正在寻找一个最小的解决方案,很好,但这也需要您方面提供一个最小的可复制示例:)。请给出一个返回复杂过程下一个值的复杂函数的示例
您的复杂过程到底是什么?他们是否计算量大(例如,解物理方程)、大量读/写磁盘、网络操作?)计算量大,但没有文件I/O或网络。两个简要说明:1。代码中有两个bug。在映射之后需要pool.close(),最后就是pool.join()。2.除了无法在调用之间继续使用生成器外,这部分工作正常。每次执行时,它停止的状态都会被删除,从而有点破坏生成器的点。1。连接和关闭的良好点,将更新答案。2.我知道状态丢失了,因此我提到它的唯一相似之处就是传递发电机。此解决方案适用于“一次性使用”发电机。我将添加另一条注释来澄清这一点。\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
方法仍然不能成为生成器。生成器是一个迭代器,它在(暂停的)函数中保持状态。您只需实现一个