Python: come posso eseguire le funzioni Python in parallelo?


109

Ho cercato prima e non sono riuscito a trovare una risposta alla mia domanda. Sto cercando di eseguire più funzioni in parallelo in Python.

Ho qualcosa del genere:

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)

Voglio chiamare func1 e func2 e farli funzionare allo stesso tempo. Le funzioni non interagiscono tra loro o sullo stesso oggetto. In questo momento devo aspettare che func1 finisca prima che func2 inizi. Come faccio a fare qualcosa come di seguito:

process.py

from files import func1, func2

runBothFunc(func1(), func2())

Voglio essere in grado di creare entrambe le directory abbastanza vicine allo stesso tempo perché ogni minuto conto quanti file vengono creati. Se la directory non è presente, annullerà il mio tempismo.


1
Potresti volerlo riprogettare; se stai contando il numero di file / cartelle ogni minuto, stai creando una condizione di competizione. Che ne dici di fare in modo che ogni funzione aggiorni un contatore o utilizzi un file di blocco per garantire che il processo periodico non aggiorni il conteggio fino al termine dell'esecuzione di entrambe le funzioni?

Risposte:


164

Potresti usare threadingo multiprocessing.

A causa delle peculiarità di CPython , threadingè improbabile che raggiunga un vero parallelismo. Per questo motivo, multiprocessingè generalmente una scommessa migliore.

Ecco un esempio completo:

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

I meccanismi di avvio / unione di processi figlio possono essere facilmente incapsulati in una funzione sulla falsariga di 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
Ho usato il tuo codice ma le funzioni ancora non sono state avviate nello stesso momento.
lmcadory

4
@Lamar McAdory: Per favore, spiega cosa intendi esattamente con "allo stesso tempo", forse fornendo un esempio concreto di quello che hai fatto, di quello che ti aspettavi che accadesse e di quello che è realmente accaduto.
NPE

4
@Lamar: Non puoi mai avere alcuna garanzia di "esattamente lo stesso tempo" e pensare di poterlo fare è semplicemente sbagliato. A seconda di quante CPU si hanno, il carico della macchina, il tempismo di molte cose che accadono sul computer avranno tutte un'influenza sul tempo di avvio dei thread / processo. Inoltre, poiché i processi vengono avviati subito dopo la creazione, anche il sovraccarico della creazione di un processo deve essere calcolato nella differenza di orario che vedi.
Martin

1
è possibile ottenere un elenco dei risultati di ciascuna funzione? diciamo che ogni funzione restituisce un valore diverso, i valori possono essere aggiunti a un elenco che può essere utilizzato in seguito? magari aggiungendo il risultato a un elenco globale?
pelos

1
Se le mie funzioni accettano parametri e quando li passo mentre li chiamo da processi separati, non vengono eseguiti contemporaneamente. Potete aiutarmi per favore
user2910372

18

Questo può essere fatto elegantemente con Ray , un sistema che ti permette di parallelizzare e distribuire facilmente il tuo codice Python.

Per parallelizzare il tuo esempio, dovresti definire le tue funzioni con il @ray.remotedecoratore e quindi richiamarle con .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)]) 

Se si passa lo stesso argomento a entrambe le funzioni e l'argomento è grande, un modo più efficiente per farlo è usare ray.put(). Questo evita che l'argomento grande venga serializzato due volte e che ne crei due copie in memoria:

largeData_id = ray.put(largeData)

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

Se func1()e func2()restituire risultati, è necessario riscrivere il codice come segue:

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

Ci sono una serie di vantaggi nell'usare Ray rispetto al modulo multiprocessing . In particolare, lo stesso codice verrà eseguito su una singola macchina così come su un cluster di macchine. Per ulteriori vantaggi di Ray, vedere questo post correlato .


18

Se le tue funzioni svolgono principalmente operazioni di I / O (e meno lavoro sulla CPU) e hai Python 3.2+, puoi usare 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!'),
])

Se le tue funzioni lavorano principalmente con la CPU (e meno lavoro di I / O) e hai Python 2.6+, puoi usare il modulo multiprocessing :

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

Questa è una buona risposta. Come identificare dal risultato le attività associate all'I / O utilizzando concurrent.futures quale è stata completata? Fondamentalmente invece delle funzioni lamba se abbiamo funzioni normali, come identificare il risultato mappato alla funzione chiamata?
Tragaknight

Non importa, ho trovato un modo - invece di questo run_cpu_tasks_in_parallel ([lambda: print ('CPU task 1 running!'), Lambda: print ('CPU task 2 running!'),]) Usa questo - results = run_io_tasks_in_parallel ([lambda: {'is_something1': func1 ()}, lambda: {'is_something2': func2 ()},])
Tragaknight

5

Se sei un utente Windows e utilizzi python 3, questo post ti aiuterà a fare la programmazione parallela in python. Quando esegui una normale programmazione di pool di librerie multiprocessing, riceverai un errore relativo alla funzione principale nel tuo programma. Questo perché il fatto che Windows non ha funzionalità fork (). Il post sottostante fornisce una soluzione al problema menzionato.

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

Dato che stavo usando python 3, ho cambiato un po 'il programma in questo modo:

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)

Dopo questa funzione, anche il codice del problema sopra viene modificato un po 'in questo modo:

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

E ho ottenuto l'output come:

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

Penso che questo post possa essere utile per alcuni utenti di Windows.


4

Non c'è modo di garantire che due funzioni vengano eseguite in sincronia tra loro, il che sembra essere quello che vuoi fare.

Il meglio che puoi fare è suddividere la funzione in più passaggi, quindi attendere che entrambi finiscano in punti critici di sincronizzazione usando Process.joincome menziona la risposta di @ aix.

È meglio che time.sleep(10)perché non puoi garantire i tempi esatti. Con l'attesa esplicita, stai dicendo che le funzioni devono essere eseguite eseguendo quel passaggio prima di passare a quello successivo, invece di presumere che verrà eseguito entro 10 ms, il che non è garantito in base a cos'altro sta succedendo sulla macchina.


1

Sembra che tu abbia una singola funzione che devi chiamare su due diversi parametri. Questo può essere fatto elegantemente usando una combinazione di concurrent.futurese mapcon Python 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]

Ora, se la tua operazione è vincolata a IO, puoi usare ThreadPoolExecutorcome tale:

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

Nota come mapviene utilizzato qui per la maptua funzione nell'elenco degli argomenti.

Ora, se la tua funzione è vincolata alla CPU, puoi usare ProcessPoolExecutor

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

Se non sei sicuro, puoi semplicemente provare entrambi e vedere quale ti dà risultati migliori.

Infine, se stai cercando di stampare i tuoi risultati, puoi semplicemente fare questo:

with ThreadPoolExecutor() as executor:
  results = executor.map(sleep_secs, secs_list)
  for result in results:
    print(result)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.