Python: Python işlevlerini paralel olarak nasıl çalıştırabilirim?


109

Önce araştırdım ve soruma cevap bulamadım. Python'da birden çok işlevi paralel olarak çalıştırmaya çalışıyorum.

Bende böyle bir şey var:

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 ve func2'yi çağırmak ve aynı anda çalıştırmalarını istiyorum. İşlevler birbirleriyle veya aynı nesne üzerinde etkileşime girmez. Şu anda func2'nin başlamasından önce func1'in bitmesini beklemem gerekiyor. Aşağıdaki gibi bir şeyi nasıl yaparım:

process.py

from files import func1, func2

runBothFunc(func1(), func2())

Her iki dizini de hemen hemen aynı anda oluşturabilmek istiyorum çünkü her dakika kaç dosyanın oluşturulduğunu sayıyorum. Dizin orada değilse, zamanlamamı bozar.


1
Bunu yeniden tasarlamak isteyebilirsiniz; her dakika dosya / klasör sayısını sayıyorsanız, bir yarış koşulu oluşturuyorsunuz. Her işlevin bir sayacı güncellemesine veya periyodik işlemin her iki işlevin yürütülmesi bitene kadar sayımı güncellememesini sağlamak için bir kilit dosyası kullanmaya ne dersiniz?

Yanıtlar:


164

Sen kullanabilirsiniz threadingveya multiprocessing.

CPython'un özelliklerinden dolayı , threadinggerçek paralelliği sağlama olasılığı düşüktür. Bu nedenle, multiprocessinggenellikle daha iyi bir bahistir.

İşte eksiksiz bir örnek:

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()

Alt süreçleri başlatma / birleştirme mekaniği, aşağıdakilerinizin satırları boyunca bir işleve kolayca dahil edilebilir 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
Kodunuzu kullandım ama işlevler hala aynı anda başlamadı.
lmcadory

4
@Lamar McAdory: Lütfen "aynı anda" ile tam olarak neyi kastettiğinizi açıklayın, belki de ne yaptığınız, ne olmasını beklediğiniz ve gerçekte ne olduğuna dair somut bir örnek verin.
NPE

4
@Lamar: "Tam olarak aynı zaman" garantisine asla sahip olamazsınız ve yapabileceğinizi düşünmek tamamen yanlıştır. Kaç cpus'unuz olduğuna bağlı olarak, makinenin yükü, bilgisayarda gerçekleşen birçok şeyin zamanlaması, iş parçacığı / sürecin başlama zamanı üzerinde etkili olacaktır. Ayrıca süreçler oluşturulduktan hemen sonra başladığından, bir süreç yaratmanın ek yükü de gördüğünüz zaman farkında hesaplanmalıdır.
Martin

1
her işlevin sonuçlarının bir listesini almak mümkün mü? diyelim her işlev farklı bir değer döndürür, değerler daha sonra kullanılabilecek bir listeye eklenebilir mi? belki sonucu genel bir listeye ekleyerek?
pelos

1
Fonksiyonlarım parametre alırsa ve onları ayrı işlemlerden çağırırken parametre geçirdiğimde aynı anda çalışmıyorlar. Lütfen yardım edebilir misin
user2910372

18

Bu, Python kodunuzu kolayca paralelleştirmenize ve dağıtmanıza olanak tanıyan bir sistem olan Ray ile zarif bir şekilde yapılabilir .

Örneğinizi paralelleştirmek için, işlevlerinizi @ray.remotedekoratörle tanımlamanız ve ardından bunları çağırmanız gerekir .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)]) 

Her iki fonksiyona da aynı argümanı iletirseniz ve argüman büyükse, bunu yapmanın daha verimli bir yolu kullanmaktır ray.put(). Bu, büyük bağımsız değişkenin iki kez serileştirilmesini ve bunun iki bellek kopyasını oluşturmasını önler:

largeData_id = ray.put(largeData)

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

Eğer func1()ve func2()dönüş sonuçları, aşağıdaki gibi bir kod yeniden yazmak gerekir:

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

Ray'i çoklu işlem modülüne göre kullanmanın birçok avantajı vardır . Özellikle, aynı kod tek bir makinede ve bir grup makinede çalışacaktır. Ray'in daha fazla avantajı için bu ilgili gönderiye bakın .


18

İşlevleriniz esas olarak G / Ç çalışması yapıyorsa (ve daha az CPU çalışması yapıyorsa) ve Python 3.2+ sürümünüz varsa, bir ThreadPoolExecutor kullanabilirsiniz :

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

İşlevleriniz esas olarak CPU işi yapıyor (ve daha az G / Ç çalışması) ve Python 2.6+ sürümünüz varsa, çoklu işlem modülünü kullanabilirsiniz :

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

Bu iyi bir cevap. I / O bağlı görevler için sonuçtan eşzamanlı. Gelecekler kullanılarak hangisinin tamamlandığı nasıl belirlenir? Temelde lamba fonksiyonları yerine normal fonksiyonlara sahipsek, çağrılan fonksiyona eşlenen sonucu nasıl belirleyebiliriz?
Tragaknight

Boşver bir yol buldum - bunun yerine run_cpu_tasks_in_parallel ([lambda: print ('CPU görevi 1 çalışıyor!'), Lambda: print ('CPU görevi 2 çalışıyor!'),]) Bunu kullanın - results = run_io_tasks_in_parallel ([lambda: {'is_something1': func1 ()}, lambda: {'is_something2': func2 ()},])
Tragaknight

5

Windows kullanıcısıysanız ve python 3 kullanıyorsanız, bu gönderi python'da paralel programlama yapmanıza yardımcı olacaktır. Olağan bir çoklu işlem kütüphanesinin havuz programlamasını çalıştırdığınızda, programınızdaki ana işlevle ilgili bir hata alırsınız. Bunun nedeni, pencerelerin fork () işlevine sahip olmamasıdır. Aşağıdaki yazı bahsedilen soruna bir çözüm sunmaktadır.

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

Python 3 kullandığım için programı şu şekilde biraz değiştirdim:

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)

Bu fonksiyondan sonra yukarıdaki problem kodu da şu şekilde biraz değiştirilir:

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])

Ve çıktıyı şu şekilde aldım:

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

Bu yazının bazı Windows kullanıcıları için faydalı olabileceğini düşünüyorum.


4

Yapmak istediğiniz şey gibi görünen iki işlevin birbiriyle senkronize çalışacağını garanti etmenin bir yolu yoktur.

Yapabileceğiniz en iyi şey, işlevi birkaç adıma ayırmak, ardından her ikisinin de Process.join@ aix'in yanıt sözlerini kullanarak kritik senkronizasyon noktalarında bitmesini beklemektir .

Bu, time.sleep(10)kesin zamanlamaları garanti edememenizden daha iyidir . Açıkça bekleyerek, makinede başka neler olup bittiğine bağlı olarak garanti edilmeyen 10 ms içinde yapılacağını varsaymak yerine, bir sonrakine geçmeden önce bu adımı yürütmek için işlevlerin yapılması gerektiğini söylüyorsunuz.


1

Görünüşe göre iki farklı parametreyi çağırmanız gereken tek bir fonksiyonunuz var. Şık bir kombinasyonu kullanılarak yapılabilir concurrent.futuresve mapPython ile 3.2+

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]

Şimdi, operasyonunuz IO'ya bağlıysa, şu şekilde kullanabilirsiniz ThreadPoolExecutor:

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

mapBurada mapişlevinizin bağımsız değişkenler listesine nasıl kullanıldığına dikkat edin.

Şimdi, işleviniz CPU'ya bağlıysa, o zaman kullanabilirsiniz ProcessPoolExecutor

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

Emin değilseniz, ikisini de deneyebilir ve hangisinin size daha iyi sonuçlar verdiğini görebilirsiniz.

Son olarak, sonuçlarınızı yazdırmak istiyorsanız, şunu yapabilirsiniz:

with ThreadPoolExecutor() as executor:
  results = executor.map(sleep_secs, secs_list)
  for result in results:
    print(result)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.