动态地向工作池添加参数

时间:2014-10-09 10:28:44

标签: python multiprocess

我正在寻找一种在同一次迭代中动态地向工作池添加参数的方法。因此,在其中一些失败的情况下,我能够立即重新处理它。

from numpy import random
from multiprocessing import Pool
from time import sleep

def foo(x):
    sleep(0.1)
    # 50% chance to have a fault
    return x, x if random.rand() > 0.5 else -1

random.seed(3)      # seed
pool = Pool(2)      # process
args = range(5)     # arguments to process

for i,(id,x) in enumerate(pool.imap(foo, args)):
    print i,x
    if x != -1:
        args.remove(id)

print args

输出

0 0
1 1
2 2
3 3
4 -1
[4]

但我希望它是

0 0
1 1
2 2
3 3
4 -1
5, 4
[]

在同一次迭代中。我的意思是,一旦迭代完成,我不想为同一个工作池创建一个新的地图。我想直接推新参数,以便在第一次迭代时失败,我不必等到结束才能使用可用的进程!我希望它有意义......

更新 我上面的问题简化了,“foo”函数大约需要20分钟才能完成,并且它分布在同时运行的24个进程中。一旦一个进程失败,我需要尽快重新处理,因为当我有可用资源时我不想等待20分钟。

2 个答案:

答案 0 :(得分:1)

据我所知,您无法将任务添加到当前正在运行的Pool(不会创建竞争条件或未定义的行为,因为您当前正在查看)。幸运的是,由于您需要做的只是重试任何失败的任务,直到成功完成,您实际上需要才能向Pool添加任何内容。您需要做的就是修改映射的函数以按照您想要的方式运行。

def foo(x):
    sleep(0.1)
    # 50% chance to have a fault
    return x, x if random.rand() > 0.5 else -1

def successful_foo(x):
    '''Version of the foo(x) function that never fails.'''

    result = -1
    while result == -1:
        result = foo(x)
    return result

现在您可以pool.imap(successful_foo, args),并确保每个流程都能成功完成(或永久运行)。如果它可能永远运行并且你想要一个选项在经过一些尝试或一些时间后中止,只需用适当的计数器或计时器替换while循环。


当然,在许多非演示案例中,具有指示失败的特殊返回值是不切实际的。在那种情况下,我更喜欢使用专门的Exception来处理您可能遇到的各种可预测的故障:

class FooError(BaseException):
    pass

def foo(x):
    sleep(0.1)
    # 50% chance to have a fault
    if random.rand() > 0.5:  # fault condition
        raise FooError('foo had an error!')
    return x, x

def successful_foo(x):
    '''Version of the foo(x) function that never fails.'''

    while True:
        try:
            return foo(x)
        except FooError as e:
            pass  # Log appropriately here; etc.

答案 1 :(得分:0)

你不能。您希望在迭代期间修改可变列表,这对于无法正常工作是已知的。 您获得的输出是由于当您remove项目形成列表时,列表会将其长度减少1,并且您删除的项目之后的所有项目都会移动一个索引。 这意味着跳过后面的项目

问题与multiprocessing本身无关,而是与普通列表无关:

In [1]: def f(x):
   ...:     print(x)
   ...:     

In [2]: args = [0, 1, 2, 3, 4, 5]

In [3]: for i, x in enumerate(args):
   ...:     print(i, x)
   ...:     if x % 2 == 0:
   ...:         args.remove(x)
   ...:         
0 0
1 2
2 4

In [4]: args
Out[4]: [1, 3, 5]

注意循环如何仅在上迭代偶数值并且从未看到奇数值。

您希望跟踪要删除的项目,并且只在循环结束时执行此操作:

to_be_removed = []
for i, (ident, x) in enumerate(pool.imap(foo, args)):
    print(i, x)
    if x != -1:
        to_be_removed.append(ident)

for ident in to_be_removed:
    args.remove(ident)

或者,可能更有效率,您可以使用set并重新构建args列表:

to_be_removed = set()
for i, (ident, x) in enumerate(pool.imap(foo, args)):
    print(i, x)
    if x != -1:
        to_be_removed.add(ident)

args = [el for el in args if el not in to_be_removed]

这需要线性时间,而不是先前解决方案的二次时间。


你也可以创建一个自定义迭代器,它可以做任意复杂的决定,决定每次迭代产生哪些元素,但是我不确定这是否适用于multiprocessing因为我认为它不会逐个使用项目(否则它将无法并行化),因此您无法保证在您期望它们时实际看到修改。

此外这样的事情只是要求错误。