如何使用多处理在for循环中并行化两个具有不同参数的函数的调用?

时间:2018-03-07 06:01:53

标签: python python-3.x ubuntu for-loop multiprocessing

在for循环中,我调用一个函数两次,但使用不同的参数集(argSet1argSet2,它们在for循环的每次迭代中都会发生变化。我想并行化这个操作,因为一组参数导致被调用函数运行得更快,而另一组参数导致函数运行缓慢。请注意,我希望为此操作设置两个for循环。我还有另一个要求:这些函数中的每一个都将执行一些并行操作,因此我不希望任何argSet1argSet2的函数运行多次,因为计算我有限的资源。确保两个参数集的函数都在运行,这将有助于我尽可能地利用CPU核心。以下是没有并行化的常规方法:

def myFunc(arg1, arg2):
    if arg1:
        print ('do something that does not take too long')
    else:
       print ('do something that takes long')

for i in range(10):
    argSet1 = arg1Storage[i]
    argSet1 = arg2Storage[i]
    myFunc(argSet1)
    myFunc(argSet2)

这绝对不会占用我拥有的计算资源。这是我尝试并行化操作:

from multiprocessing import Process

def myFunc(arg1, arg2):
    if arg1:
        print ('do something that does not take too long')
    else:
       print ('do something that takes long')

for i in range(10):
    argSet1 = arg1Storage[i]
    argSet1 = arg2Storage[i]
    p1 = Process(target=myFunc, args=argSet1)
    p1.start()
    p2 = Process(target=myFunc, args=argSet2)
    p2.start()

然而,这样每个函数及其各自的参数将被调用10次,并且事情变得极其缓慢。鉴于我对多处理的了解有限,我尝试通过在for循环的末尾添加p1.join()p2.join()来进一步改进,但这仍然会导致p1做得更慢更快,事情等到p2完成。我还考虑过使用multiprocessing.Value与函数进行一些通信,但是我必须在函数内部为每个函数调用添加一个while循环,这会再次降低所有内容的速度。我想知道是否有人可以提供实用的解决方案?

3 个答案:

答案 0 :(得分:2)

您可能希望使用multiprocessing.Pool个流程并将myFunc映射到其中,如下所示:

from multiprocessing import Pool
import time

def myFunc(arg1, arg2):
    if arg1:
        print ('do something that does not take too long')
        time.sleep(0.01)
    else:
       print ('do something that takes long')
       time.sleep(1)

def wrap(args):
    return myFunc(*args)

if __name__ == "__main__":
    p = Pool()
    argStorage = [(True, False), (False, True)] * 12
    p.map(wrap, argStorage)

我添加了wrap函数,因为传递给p.map的函数必须接受单个参数。你也可以调整myFunc来接受一个元组,如果你的情况可以的话。

我的样本appStorage由24个项组成,其中12个将需要1秒来处理,12个将在10毫秒内完成。总的来说,这个脚本运行3-4秒(我有4个核心)。

答案 1 :(得分:1)

由于我在补丁中构建了这个答案,请向下滚动以找到解决此问题的最佳方法

您需要指定完全您希望如何运行。据我所知,您希望最多运行两个进程,但至少也是如此。此外,你不希望沉重的呼叫阻止快速呼叫。一种简单的非最佳运行方式是:

from multiprocessing import Process

def func(counter,somearg):
    j = 0
    for i in range(counter): j+=i
    print(somearg)

def loop(counter,arglist):
    for i in range(10):
        func(counter,arglist[i])

heavy = Process(target=loop,args=[1000000,['heavy'+str(i) for i in range(10)]])
light = Process(target=loop,args=[500000,['light'+str(i) for i in range(10)]])
heavy.start()
light.start()
heavy.join()
light.join()

这里的输出是(例如运行):

light0
heavy0
light1
light2
heavy1
light3
light4
heavy2
light5
light6
heavy3
light7
light8
heavy4
light9
heavy5
heavy6
heavy7
heavy8
heavy9

你可以看到最后一部分是次优的,因为你有一系列繁重的运行 - 这意味着有一个进程而不是两个进程。

如果您可以估算重型流程运行的时间长度,则可以轻松优化此方法。如果它的速度是这里的两倍,那么只需先运行7次重复迭代,加入光照过程,并运行额外的3次。

另一种方法是成对运行繁重的流程,所以首先你有3个流程,直到快速流程结束,然后继续2。

主要观点将重型和轻型呼叫完全分离到另一个进程 - 因此,当快速呼叫一个接一个地快速完成时,您可以使用缓慢的工作。一旦快速结束,由你决定你想要继续多么复杂,但我认为现在估计如何打破沉重的呼叫是足够好的。这是我的例子:

from multiprocessing import Process

def func(counter,somearg):
    j = 0
    for i in range(counter): j+=i
    print(somearg)

def loop(counter,amount,arglist):
    for i in range(amount):
        func(counter,arglist[i])

heavy1 = Process(target=loop,args=[1000000,7,['heavy1'+str(i) for i in range(7)]])
light = Process(target=loop,args=[500000,10,['light'+str(i) for i in range(10)]])
heavy2 = Process(target=loop,args=[1000000,3,['heavy2'+str(i) for i in range(7,10)]])
heavy1.start()
light.start()
light.join()
heavy2.start()
heavy1.join()
heavy2.join()

带输出:

light0
heavy10
light1
light2
heavy11
light3
light4
heavy12
light5
light6
heavy13
light7
light8
heavy14
light9
heavy15
heavy27
heavy16
heavy28
heavy29

更好的利用率。您当然可以通过为慢速进程运行共享队列来使其更先进,因此当快速完成时,它们可以作为缓冲队列上的工作者加入,但是对于仅两个不同的调用,这可能是过度的(尽管使用起来并不困难) queue)。 最佳解决方案

from multiprocessing import Queue,Process
import queue

def func(index,counter,somearg):
    j = 0
    for i in range(counter): j+=i
    print("Worker",index,':',somearg)

def worker(index):
    try:
        while True:
            func,args = q.get(block=False)
            func(index,*args)
    except queue.Empty: pass

q = Queue()
for i in range(10):
    q.put((func,(500000,'light'+str(i))))
    q.put((func,(1000000,'heavy'+str(i))))

nworkers = 2
workers = []
for i in range(nworkers):
    workers.append(Process(target=worker,args=(i,)))
    workers[-1].start()
q.close()
for worker in workers:
    worker.join()

这是您想要的最佳,最具扩展性的解决方案。输出:

Worker 0 : light0
Worker 0 : light1
Worker 1 : heavy0
Worker 1 : light2
Worker 0 : heavy1
Worker 0 : light3
Worker 1 : heavy2
Worker 1 : light4
Worker 0 : heavy3
Worker 0 : light5
Worker 1 : heavy4
Worker 1 : light6
Worker 0 : heavy5
Worker 0 : light7
Worker 1 : heavy6
Worker 1 : light8
Worker 0 : heavy7
Worker 0 : light9
Worker 1 : heavy8
Worker 0 : heavy9

答案 2 :(得分:0)

一种可能的实施方式如下:

import concurrent.futures
import math

list_of_args = [arg1, arg2]

def my_func(arg):
    ....
    print ('do something that takes long')

def main():
    with concurrent.futures.ProcessPoolExecutor() as executor:
        for arg, result in zip(list_of_args, executor.map(is_prime, list_of_args)):
            print('my_func({0}) => {1}'.format(arg, result))

executor.map与内置函数类似,map方法允许多次调用提供的函数,将迭代中的每个项传递给该函数。