Python:Python関数を並列で実行するにはどうすればよいですか?


109

最初に調べたところ、質問に対する答えが見つかりませんでした。Pythonで複数の関数を並行して実行しようとしています。

私はこのようなものを持っています:

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を呼び出して、同時に実行させたい。関数は相互に、または同じオブジェクト上で相互作用しません。今私はfunc2が始まる前にfunc1が終わるのを待たなければなりません。以下のようにするにはどうすればよいですか:

process.py

from files import func1, func2

runBothFunc(func1(), func2())

作成されるファイルの数を1分ごとに数えるため、両方のディレクトリをほぼ同時に作成できるようにしたいと考えています。ディレクトリが存在しない場合は、タイミングが狂います。


1
これを再構築する必要があるかもしれません。ファイル/フォルダーの数を毎分カウントしている場合は、競合状態が発生しています。各関数でカウンターを更新したり、ロックファイルを使用して、両方の関数の実行が完了するまで定期的なプロセスがカウントを更新しないようにするにはどうすればよいですか?

回答:


163

threadingまたはを使用できますmultiprocessing

以下のためにはCPythonの特殊性threading真の並列処理を実現することはほとんどありません。このため、multiprocessing一般的にはより良い賭けです。

以下は完全な例です。

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

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)

4
私はあなたのコードを使用しましたが、関数はまだ同時に起動しませんでした。
lmcadory 2011

4
@Lamar McAdory:「同時に」の正確な意味を説明してください。おそらく、何をしたか、何が起こると予想していたか、実際に何が起こったかの具体例を挙げてください。
NPE、2011

4
@Lamar:「完全に同じ時間」という保証はありませんし、自分ができると考えるのは間違いです。CPUの数、マシンの負荷、コンピュータで発生する多くの事柄のタイミングに応じて、すべてスレッド/プロセスの開始時間に影響します。また、プロセスは作成直後に開始されるため、プロセス作成のオーバーヘッドも表示される時間差で計算する必要があります。
マーティン

1
各関数の結果のリストを取得することは可能ですか?各関数が異なる値を返すとしましょう。値を後で使用できるリストに追加できますか?多分結果をグローバルリストに追加しますか?
ペロス2017

1
私の関数がパラメーターを取る場合、別のプロセスから呼び出すときにパラメーターを渡すと、それらは同時に実行されません。あなたは助けてください
user2910372

18

これは、Pythonコードを簡単に並列化して配布できるシステムであるRayを使用してエレガントに実行できます。

例を並列化するには、@ray.remoteデコレータを使用して関数を定義してから、でそれらを呼び出す必要があり.remoteます。

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()です。これにより、大きな引数が2回シリアル化され、2つのメモリコピーが作成されるのを回避できます。

largeData_id = ray.put(largeData)

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

結果func1()func2()返す場合は、次のようにコードを書き直す必要があります。

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

マルチプロセッシングモジュールよりもレイを使用することには多くの利点があります。特に、同じコードが単一のマシンとマシンのクラスターで実行されます。Rayのその他の利点については、この関連記事を参照してください。


18

関数が主にI / O作業を行っており(CPU作業は少ない)、Python 3.2以降を使用している場合は、ThreadPoolExecutorを使用できます。

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以降を使用している場合は、マルチプロセッシングモジュールを使用できます。

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!'),
])

これは良い答えです。いずれかが完了したconcurrent.futuresを使用して、I / Oバウンドタスクの結果からどのように識別するか?基本的に、通常の関数がある場合、ランバ関数の代わりに、呼び出された関数にマッピングされた結果をどのように識別するのですか?
Tragaknight

Nevermind私は方法を見つけました-このrun_cpu_tasks_in_parallel([lambda:print( 'CPU task 1 running!')、lambda:print( 'CPU task 2 running!')、])の代わりにこれを使用してください-results = run_io_tasks_in_parallel([lambda: {'is_something1':func1()}、ラムダ:{'is_something2':func2()}、])
Tragaknight

5

あなたがWindowsユーザーであり、Python 3を使用している場合、この投稿はPythonで並列プログラミングを行うのに役立ちます。通常のマルチプロセッシングライブラリのプールプログラミングを実行すると、プログラムのメイン関数に関するエラーが発生します。これは、ウィンドウにfork()機能がないためです。以下の投稿は、言及された問題の解決策を提供しています。

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

私はpython 3を使用していたので、プログラムを次のように少し変更しました。

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)

この関数の後、上記の問題コードも次のように少し変更されます。

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, 8, 27, 64, 125, 216]

この投稿は一部のWindowsユーザーに役立つと思います。


4

2つの関数が互いに同期して実行されることを保証する方法はありません。

あなたができる最善のことは、関数をいくつかのステップに分割し、Process.join@ aixの回答の言及を使用して重要な同期ポイントで両方が完了するのを待つことです。

これは、time.sleep(10)正確なタイミングを保証できないためよりも優れています。明示的に待機していると、関数は次のステップに進む前にそのステップを実行して実行する必要があると言います。マシンで他に何が行われているのかによって保証されない10ミリ秒以内に実行されると想定するのではありません。


1

2つの異なるパラメーターで呼び出す必要がある単一の関数があるようです。これは、Python 3.2以降concurrent.futuresとの組み合わせを使用してエレガントに行うことができますmap

import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor

def sleep_secs(seconds):
  time.sleep(seconds)
  print(f'{seconds} has been processed')

secs_list = [2,4, 6, 8, 10, 12]

これで、操作がIOバウンドである場合、次のように使用できますThreadPoolExecutor

with ThreadPoolExecutor() as executor:
  results = executor.map(sleep_secs, secs_list)

mapここmapで、引数のリストに対して関数をどのように使用するかに注意してください。

ここで、関数がCPUバウンドの場合、使用できます ProcessPoolExecutor

with ProcessPoolExecutor() as executor:
  results = executor.map(sleep_secs, secs_list)

よくわからない場合は、両方を試して、どちらがより良い結果をもたらすかを確認してください。

最後に、結果を印刷したい場合は、次のようにするだけです。

with ThreadPoolExecutor() as executor:
  results = executor.map(sleep_secs, secs_list)
  for result in results:
    print(result)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.