Python:如何并行运行python函数?

Python: How can I run python functions in parallel?

我先研究了一下,却找不到答案。 我试图在Python中并行运行多个函数。

我有这样的事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
files.py

import common #common is a util class that handles all the IO stuff

dir1 = 'C:\folder1'
dir2 = 'C:\folder2'
filename = 'test.txt'
addFiles = [25, 5, 15, 35, 45, 25, 5, 15, 35, 45]

def func1():
   c = common.Common()
   for i in range(len(addFiles)):
       c.createFiles(addFiles[i], filename, dir1)
       c.getFiles(dir1)
       time.sleep(10)
       c.removeFiles(addFiles[i], dir1)
       c.getFiles(dir1)

def func2():
   c = common.Common()
   for i in range(len(addFiles)):
       c.createFiles(addFiles[i], filename, dir2)
       c.getFiles(dir2)
       time.sleep(10)
       c.removeFiles(addFiles[i], dir2)
       c.getFiles(dir2)

我想调用func1和func2并使它们同时运行。 这些功能彼此之间或在同一对象上不相互作用。 现在,我必须等待func1完成才能启动func2。 我该如何执行以下操作:

1
2
3
4
5
process.py

from files import func1, func2

runBothFunc(func1(), func2())

我希望能够几乎同时创建两个目录,因为我每分钟都在统计要创建多少个文件。 如果该目录不存在,它将使我的时间安排变慢。


您可以使用threadingmultiprocessing

由于CPython的特殊性,threading不太可能实现真正的并行性。因此,multiprocessing通常是更好的选择。

这是一个完整的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from multiprocessing import Process

def func1():
  print 'func1: starting'
  for i in xrange(10000000): pass
  print 'func1: finishing'

def func2():
  print 'func2: starting'
  for i in xrange(10000000): pass
  print 'func2: finishing'

if __name__ == '__main__':
  p1 = Process(target=func1)
  p1.start()
  p2 = Process(target=func2)
  p2.start()
  p1.join()
  p2.join()

启动/联接子进程的机制可以很容易地按照runBothFunc的方式封装到一个函数中:

1
2
3
4
5
6
7
8
9
10
def runInParallel(*fns):
  proc = []
  for fn in fns:
    p = Process(target=fn)
    p.start()
    proc.append(p)
  for p in proc:
    p.join()

runInParallel(func1, func2)


可以使用Ray优雅地完成此任务,该系统使您可以轻松地并行化和分发Python代码。

要并行化示例,您需要使用@ray.remote装饰器定义函数,然后使用.remote调用它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import ray

ray.init()

dir1 = 'C:\\folder1'
dir2 = 'C:\\folder2'
filename = 'test.txt'
addFiles = [25, 5, 15, 35, 45, 25, 5, 15, 35, 45]

# Define the functions.
# You need to pass every global variable used by the function as an argument.
# This is needed because each remote function runs in a different process,
# and thus it does not have access to the global variables defined in
# the current process.
@ray.remote
def func1(filename, addFiles, dir):
    # func1() code here...

@ray.remote
def func2(filename, addFiles, dir):
    # func2() code here...

# Start two tasks in the background and wait for them to finish.
ray.get([func1.remote(filename, addFiles, dir1), func2.remote(filename, addFiles, dir2)])

如果将相同的参数传递给两个函数且参数较大,则更有效的方法是使用ray.put()。这样可以避免将大参数序列化两次并为其创建两个内存副本:

1
2
3
largeData_id = ray.put(largeData)

ray.get([func1(largeData_id), func2(largeData_id)])

如果func1()func2()返回结果,则需要按以下方式重写代码:

1
2
3
ret_id1 = func1.remote(filename, addFiles, dir1)
ret_id2 = func1.remote(filename, addFiles, dir2)
ret1, ret2 = ray.get([ret_id1, ret_id2])

与多处理模块相比,使用Ray有许多优点。特别是,相同的代码将在单台计算机以及一台计算机集群上运行。有关Ray的更多优点,请参见此相关文章。


如果您是Windows用户并且使用python 3,那么本文将帮助您在python中进行并行编程。当您运行常规的多处理库的池编程时,您将在程序中遇到关于主要功能的错误。这是因为Windows没有fork()功能。下面的帖子提供了上述问题的解决方案。

http://python.6.x6.nabble.com/Multiprocessing-Pool-woes-td5047050.html

由于我使用的是python 3,因此我对该程序进行了如下更改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from types import FunctionType
import marshal

def _applicable(*args, **kwargs):
  name = kwargs['__pw_name']
  code = marshal.loads(kwargs['__pw_code'])
  gbls = globals() #gbls = marshal.loads(kwargs['__pw_gbls'])
  defs = marshal.loads(kwargs['__pw_defs'])
  clsr = marshal.loads(kwargs['__pw_clsr'])
  fdct = marshal.loads(kwargs['__pw_fdct'])
  func = FunctionType(code, gbls, name, defs, clsr)
  func.fdct = fdct
  del kwargs['__pw_name']
  del kwargs['__pw_code']
  del kwargs['__pw_defs']
  del kwargs['__pw_clsr']
  del kwargs['__pw_fdct']
  return func(*args, **kwargs)

def make_applicable(f, *args, **kwargs):
  if not isinstance(f, FunctionType): raise ValueError('argument must be a function')
  kwargs['__pw_name'] = f.__name__  # edited
  kwargs['__pw_code'] = marshal.dumps(f.__code__)   # edited
  kwargs['__pw_defs'] = marshal.dumps(f.__defaults__)  # edited
  kwargs['__pw_clsr'] = marshal.dumps(f.__closure__)  # edited
  kwargs['__pw_fdct'] = marshal.dumps(f.__dict__)   # edited
  return _applicable, args, kwargs

def _mappable(x):
  x,name,code,defs,clsr,fdct = x
  code = marshal.loads(code)
  gbls = globals() #gbls = marshal.loads(gbls)
  defs = marshal.loads(defs)
  clsr = marshal.loads(clsr)
  fdct = marshal.loads(fdct)
  func = FunctionType(code, gbls, name, defs, clsr)
  func.fdct = fdct
  return func(x)

def make_mappable(f, iterable):
  if not isinstance(f, FunctionType): raise ValueError('argument must be a function')
  name = f.__name__    # edited
  code = marshal.dumps(f.__code__)   # edited
  defs = marshal.dumps(f.__defaults__)  # edited
  clsr = marshal.dumps(f.__closure__)  # edited
  fdct = marshal.dumps(f.__dict__)  # edited
  return _mappable, ((i,name,code,defs,clsr,fdct) for i in iterable)

使用此功能后,上面的问题代码也做了如下更改:

1
2
3
4
5
6
7
8
9
10
from multiprocessing import Pool
from poolable import make_applicable, make_mappable

def cube(x):
  return x**3

if __name__ =="__main__":
  pool    = Pool(processes=2)
  results = [pool.apply_async(*make_applicable(cube,x)) for x in range(1,7)]
  print([result.get(timeout=10) for result in results])

我得到的输出为:

1
[1, 8, 27, 64, 125, 216]

我认为这篇文章可能对某些Windows用户有用。


无法保证两个函数将彼此同步执行,这似乎是您想要执行的操作。

最好的办法是将函数分成几个步骤,然后使用Process.join像@aix的答案提到的那样,在关键的同步点上等待两个步骤都完成。

这比time.sleep(10)更好,因为您不能保证确切的时间。在显式等待的情况下,您要说的是必须在执行该步骤之前完成功能,然后再移至下一个步骤,而不是假设它将在10毫秒内完成,而这不能根据计算机上发生的其他事情来保证。


如果您的函数主要用于执行I / O工作(而减少了CPU工作),并且您拥有Python 3.2+,则可以使用ThreadPoolExecutor:

1
2
3
4
5
6
7
8
9
10
11
12
from concurrent.futures import ThreadPoolExecutor

def run_io_tasks_in_parallel(tasks):
    with ThreadPoolExecutor() as executor:
        running_tasks = [executor.submit(task) for task in tasks]
        for running_task in running_tasks:
            running_task.result()

run_io_tasks_in_parallel([
    lambda: print('IO task 1 running!'),
    lambda: print('IO task 2 running!'),
])

如果您的功能主要是在做CPU工作(而I / O则更少),并且您拥有Python 2.6+,则可以使用多处理模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
from multiprocessing import Process

def run_cpu_tasks_in_parallel(tasks):
    running_tasks = [Process(target=task) for task in tasks]
    for running_task in running_tasks:
        running_task.start()
    for running_task in running_tasks:
        running_task.join()

run_cpu_tasks_in_parallel([
    lambda: print('CPU task 1 running!'),
    lambda: print('CPU task 2 running!'),
])