Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/341.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 将难堪的可并联发电机并联的简单方法_Python_Parallel Processing_Generator - Fatal编程技术网

Python 将难堪的可并联发电机并联的简单方法

Python 将难堪的可并联发电机并联的简单方法,python,parallel-processing,generator,Python,Parallel Processing,Generator,我有一个发电机(或者,一个发电机列表)。让我们称他们为氏族 gens中的每个生成器都是一个复杂的函数,返回复杂过程的下一个值。幸运的是,它们彼此独立 我想为gens中的每个元素gen调用gen.\uuu next\uuu(),并在列表中返回结果值。然而,酸洗发生器的多重处理并不令人满意 在Python中是否有一种快速、简单的方法来实现这一点?我希望长度为m的gens在我的机器上本地映射到n个核,其中n可以大于或小于m。每台发电机应在单独的铁芯上运行 如果这是可能的,有人能提供一个简单的例子吗?如

我有一个发电机(或者,一个发电机列表)。让我们称他们为氏族

gens中的每个生成器都是一个复杂的函数,返回复杂过程的下一个值。幸运的是,它们彼此独立

我想为
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。因此,在这种情况下,您仍然需要提供定制的getstatesetstate方法。此问题使得发电机的酸洗支持相当有限

他还提出了一个解决方案,使用简单的迭代器

这个问题的最佳解决方案是将生成器重写为简单的迭代器(即,使用
\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
    方法仍然不能成为生成器。生成器是一个迭代器,它在(暂停的)函数中保持状态。您只需实现一个