Come posso recuperare il valore di ritorno di una funzione passata a multiprocessing.Process?


190

Nel seguente codice di esempio, vorrei ripristinare il valore restituito della funzione worker. Come posso fare per fare questo? Dove è memorizzato questo valore?

Codice di esempio:

import multiprocessing

def worker(procnum):
    '''worker function'''
    print str(procnum) + ' represent!'
    return procnum


if __name__ == '__main__':
    jobs = []
    for i in range(5):
        p = multiprocessing.Process(target=worker, args=(i,))
        jobs.append(p)
        p.start()

    for proc in jobs:
        proc.join()
    print jobs

Produzione:

0 represent!
1 represent!
2 represent!
3 represent!
4 represent!
[<Process(Process-1, stopped)>, <Process(Process-2, stopped)>, <Process(Process-3, stopped)>, <Process(Process-4, stopped)>, <Process(Process-5, stopped)>]

Non riesco a trovare l'attributo rilevante negli oggetti memorizzati jobs.

Risposte:


189

Usa la variabile condivisa per comunicare. Ad esempio in questo modo:

import multiprocessing

def worker(procnum, return_dict):
    '''worker function'''
    print str(procnum) + ' represent!'
    return_dict[procnum] = procnum


if __name__ == '__main__':
    manager = multiprocessing.Manager()
    return_dict = manager.dict()
    jobs = []
    for i in range(5):
        p = multiprocessing.Process(target=worker, args=(i,return_dict))
        jobs.append(p)
        p.start()

    for proc in jobs:
        proc.join()
    print return_dict.values()

46
Consiglierei di usare a multiprocessing.Queue, piuttosto che Managerqui. L'uso di a Managerrichiede la generazione di un processo completamente nuovo, che è eccessivo quando Queuelo farebbe.
dano,

1
@dano: Mi chiedo, se usiamo l'oggetto Queue (), non possiamo essere sicuri dell'ordine quando ogni processo restituisce il valore. Voglio dire se abbiamo bisogno dell'ordine nel risultato, per fare il prossimo lavoro. Come potremmo sapere con esattezza quale output proviene da quale processo
Catbuilts,

4
@Catbuilts Puoi restituire una tupla da ogni processo, dove un valore è il valore di ritorno effettivo che ti interessa e l'altro è un identificatore univoco dal processo. Ma mi chiedo anche perché è necessario sapere quale processo sta restituendo quale valore. Se questo è ciò che è necessario sapere sul processo o è necessario correlare tra l'elenco di input e l'elenco di output? In tal caso, consiglierei di utilizzare multiprocessing.Pool.mapper elaborare l'elenco di elementi di lavoro.
dano,

5
avvertenze per le funzioni con un solo argomento : dovrebbe usare args=(my_function_argument, ). Nota la ,virgola qui! Altrimenti Python lamenterà "argomenti posizionali mancanti". Mi ci sono voluti 10 minuti per capire. Controllare anche l' uso manuale (nella sezione "classe di processo").
yuqli,

2
@vartec uno svantaggio dell'uso di un dizionario multipriocessing.Manager () è che pickles (serializza) l'oggetto che restituisce, quindi ha un collo di bottiglia dato dalla libreria pickle di dimensione massima di 2GiB affinché l'oggetto ritorni. Esiste un altro modo per farlo evitando la serializzazione dell'oggetto di ritorno?
Hirschme,

68

Penso che l'approccio suggerito da @sega_sai sia il migliore. Ma ha davvero bisogno di un esempio di codice, quindi ecco qui:

import multiprocessing
from os import getpid

def worker(procnum):
    print('I am number %d in process %d' % (procnum, getpid()))
    return getpid()

if __name__ == '__main__':
    pool = multiprocessing.Pool(processes = 3)
    print(pool.map(worker, range(5)))

Che stamperà i valori di ritorno:

I am number 0 in process 19139
I am number 1 in process 19138
I am number 2 in process 19140
I am number 3 in process 19139
I am number 4 in process 19140
[19139, 19138, 19140, 19139, 19140]

Se hai familiarità con map(il Python 2 integrato) questo non dovrebbe essere troppo impegnativo. Altrimenti dai un'occhiata al link di sega_Sai .

Nota quanto è necessario poco codice. (Nota anche come i processi vengono riutilizzati).


1
Qualche idea sul perché il mio getpid()ritorno abbia lo stesso valore? Sto eseguendo Python3
zelusp il

Non sono sicuro di come Pool distribuisca le attività sui lavoratori. Forse possono finire tutti allo stesso lavoratore se sono davvero veloci? Succede costantemente? Anche se aggiungi un ritardo?
Segna il

Ho anche pensato che fosse una cosa legata alla velocità ma quando pool.mapnutro un intervallo di 1.000.000 usando più di 10 processi vedo al massimo due pid diversi.
zelusp,

1
Quindi non sono sicuro. Penso che sarebbe interessante aprire una domanda separata per questo.
Segna il

Se le cose che si desidera inviare una funzione diversa a ciascun processo, utilizzare pool.apply_async: docs.python.org/3/library/…
Kyle

24

Questo esempio mostra come utilizzare un elenco di istanze multiprocessing.Pipe per restituire stringhe da un numero arbitrario di processi:

import multiprocessing

def worker(procnum, send_end):
    '''worker function'''
    result = str(procnum) + ' represent!'
    print result
    send_end.send(result)

def main():
    jobs = []
    pipe_list = []
    for i in range(5):
        recv_end, send_end = multiprocessing.Pipe(False)
        p = multiprocessing.Process(target=worker, args=(i, send_end))
        jobs.append(p)
        pipe_list.append(recv_end)
        p.start()

    for proc in jobs:
        proc.join()
    result_list = [x.recv() for x in pipe_list]
    print result_list

if __name__ == '__main__':
    main()

Produzione:

0 represent!
1 represent!
2 represent!
3 represent!
4 represent!
['0 represent!', '1 represent!', '2 represent!', '3 represent!', '4 represent!']

Questa soluzione utilizza meno risorse di una multiprocessing.Queue che utilizza

  • una pipa
  • almeno una serratura
  • un buffer
  • un filo

o un multiprocessing.SimpleQueue che utilizza

  • una pipa
  • almeno una serratura

È molto istruttivo esaminare la fonte per ciascuno di questi tipi.


Quale sarebbe il modo migliore per farlo senza trasformare le pipe in una variabile globale?
Nickpick,

Ho messo tutti i dati globali e il codice in una funzione principale e funziona allo stesso modo. Questo risponde alla tua domanda?
David Cullen,

la pipe deve sempre essere letta prima di poter aggiungere (inviare) qualsiasi nuovo valore?
Nickpick,

+1, buona risposta. Ma poiché la soluzione è più efficiente, il compromesso è che ne stai realizzando uno Pipeper processo contro uno Queueper tutti i processi. Non so se questo finisca per essere più efficiente in tutti i casi.
sudo,

2
Questa risposta provoca un deadlock se l'oggetto di ritorno è grande. Invece di fare prima il proc.join () prima proverei a recv () il valore di ritorno e poi faccio il join.
L. Pes,

22

Per qualche motivo, non sono riuscito a trovare un esempio generale di come farlo Queueovunque (anche gli esempi di documenti di Python non generano più processi), quindi ecco cosa ho lavorato dopo 10 tentativi:

def add_helper(queue, arg1, arg2): # the func called in child processes
    ret = arg1 + arg2
    queue.put(ret)

def multi_add(): # spawns child processes
    q = Queue()
    processes = []
    rets = []
    for _ in range(0, 100):
        p = Process(target=add_helper, args=(q, 1, 2))
        processes.append(p)
        p.start()
    for p in processes:
        ret = q.get() # will block
        rets.append(ret)
    for p in processes:
        p.join()
    return rets

Queueè una coda bloccante, thread-safe che è possibile utilizzare per archiviare i valori restituiti dai processi figlio. Quindi devi passare la coda per ogni processo. Qualcosa di meno ovvio qui è che devi fare get()la fila prima di tejoin i Processes o altro la coda si riempie e blocchi tutto.

Aggiornamento per coloro che sono orientati agli oggetti (testato in Python 3.4):

from multiprocessing import Process, Queue

class Multiprocessor():

    def __init__(self):
        self.processes = []
        self.queue = Queue()

    @staticmethod
    def _wrapper(func, queue, args, kwargs):
        ret = func(*args, **kwargs)
        queue.put(ret)

    def run(self, func, *args, **kwargs):
        args2 = [func, self.queue, args, kwargs]
        p = Process(target=self._wrapper, args=args2)
        self.processes.append(p)
        p.start()

    def wait(self):
        rets = []
        for p in self.processes:
            ret = self.queue.get()
            rets.append(ret)
        for p in self.processes:
            p.join()
        return rets

# tester
if __name__ == "__main__":
    mp = Multiprocessor()
    num_proc = 64
    for _ in range(num_proc): # queue up multiple tasks running `sum`
        mp.run(sum, [1, 2, 3, 4, 5])
    ret = mp.wait() # get all results
    print(ret)
    assert len(ret) == num_proc and all(r == 15 for r in ret)

18

Per chiunque stia cercando come ottenere un valore da un Processutilizzo Queue:

import multiprocessing

ret = {'foo': False}

def worker(queue):
    ret = queue.get()
    ret['foo'] = True
    queue.put(ret)

if __name__ == '__main__':
    queue = multiprocessing.Queue()
    queue.put(ret)
    p = multiprocessing.Process(target=worker, args=(queue,))
    p.start()
    print queue.get()  # Prints {"foo": True}
    p.join()

1
quando inserisco qualcosa in coda nel mio processo di lavoro, il mio join non viene mai raggiunto. Qualche idea su come potrebbe venire?
Laurens Koppenol,

@LaurensKoppenol intendi che il tuo codice principale si blocca permanentemente su p.join () e non continua mai? Il tuo processo ha un ciclo infinito?
Matthew Moisen,

4
Sì, è sospeso all'infinito. Tutti i miei lavoratori finiscono (termina il ciclo all'interno della funzione lavoratore, poi viene stampata la dichiarazione di stampa, per tutti i lavoratori). Il join non fa nulla. Se rimuovo il Queuedalla mia funzione mi fa passare iljoin()
Laurens Koppenol

@LaurensKoppenol Forse non stai chiamando queue.put(ret)prima di chiamare p.start()? In tal caso, il thread di lavoro si bloccherà per queue.get()sempre. Puoi replicarlo copiando il mio frammento sopra mentre commenti queue.put(ret).
Matthew Moisen,

Ho modificato questa risposta, queue.get()deve succedere prima del p.join(). Funziona ora per me.
jfunk


10

È possibile utilizzare il exitbuilt-in per impostare il codice di uscita di un processo. Può essere ottenuto dall'attributo exitcodedel processo:

import multiprocessing

def worker(procnum):
    print str(procnum) + ' represent!'
    exit(procnum)

if __name__ == '__main__':
    jobs = []
    for i in range(5):
        p = multiprocessing.Process(target=worker, args=(i,))
        jobs.append(p)
        p.start()

    result = []
    for proc in jobs:
        proc.join()
        result.append(proc.exitcode)
    print result

Produzione:

0 represent!
1 represent!
2 represent!
3 represent!
4 represent!
[0, 1, 2, 3, 4]

4
Essere avvisati che questo approccio potrebbe diventare confuso. I processi dovrebbero generalmente uscire con il codice di uscita 0 se vengono completati senza errori. Se si verifica qualcosa che controlla i codici di uscita del processo di sistema, è possibile che vengano visualizzati come errori.
ruota di ferro

1
Perfetto se si desidera solo sollevare un'eccezione nel processo padre in caso di errore.
crizCraig


3

Ho pensato di semplificare gli esempi più semplici copiati dall'alto, lavorando per me su Py3.6. Il più semplice è multiprocessing.Pool:

import multiprocessing
import time

def worker(x):
    time.sleep(1)
    return x

pool = multiprocessing.Pool()
print(pool.map(worker, range(10)))

È possibile impostare il numero di processi in piscina con, ad esempio, Pool(processes=5). Tuttavia, per impostazione predefinita è il conteggio della CPU, quindi lasciarlo vuoto per le attività associate alla CPU. (Le attività associate a I / O spesso si adattano comunque ai thread, poiché i thread sono in gran parte in attesa, quindi possono condividere un core della CPU.) PoolApplica anche l'ottimizzazione del chunking .

(Si noti che il metodo worker non può essere nidificato all'interno di un metodo. Inizialmente ho definito il mio metodo worker all'interno del metodo che effettua la chiamata pool.map, per mantenerlo tutto autonomo, ma quindi i processi non sono riusciti a importarlo e ho lanciato "AttributeError : Impossibile decapare l'oggetto locale outer_method..inner_method ". Altro qui . Può essere all'interno di una classe.)

(Apprezzo la domanda originale specificata stampando 'represent!'piuttosto che time.sleep(), ma senza di essa pensavo che un po 'di codice fosse in esecuzione contemporaneamente quando non lo era.)


Py3 ha ProcessPoolExecutoranche due righe ( .maprestituisce un generatore quindi è necessario il list()):

from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor() as executor:
    print(list(executor.map(worker, range(10))))

Con semplici Processes:

import multiprocessing
import time

def worker(x, queue):
    time.sleep(1)
    queue.put(x)

queue = multiprocessing.SimpleQueue()
tasks = range(10)

for task in tasks:
    multiprocessing.Process(target=worker, args=(task, queue,)).start()

for _ in tasks:
    print(queue.get())

Utilizzare SimpleQueuese tutto ciò che serve è pute get. Il primo ciclo avvia tutti i processi, prima che il secondo effettui le queue.getchiamate di blocco . Non penso che ci sia alcun motivo per chiamare p.join()anche.


2

Una soluzione semplice:

import multiprocessing

output=[]
data = range(0,10)

def f(x):
    return x**2

def handler():
    p = multiprocessing.Pool(64)
    r=p.map(f, data)
    return r

if __name__ == '__main__':
    output.append(handler())

print(output[0])

Produzione:

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

2

Se stai usando Python 3, puoi usarlo concurrent.futures.ProcessPoolExecutorcome una comoda astrazione:

from concurrent.futures import ProcessPoolExecutor

def worker(procnum):
    '''worker function'''
    print(str(procnum) + ' represent!')
    return procnum


if __name__ == '__main__':
    with ProcessPoolExecutor() as executor:
        print(list(executor.map(worker, range(5))))

Produzione:

0 represent!
1 represent!
2 represent!
3 represent!
4 represent!
[0, 1, 2, 3, 4]

0

Ho modificato un po 'la risposta di Vartec poiché avevo bisogno di ottenere i codici di errore dalla funzione. (Grazie vertec !!! è un trucco fantastico)

Questo può anche essere fatto con un manager.listma penso che sia meglio averlo in un dict e memorizzare un elenco al suo interno. In questo modo, manteniamo la funzione e i risultati poiché non possiamo essere sicuri dell'ordine in cui l'elenco verrà popolato.

from multiprocessing import Process
import time
import datetime
import multiprocessing


def func1(fn, m_list):
    print 'func1: starting'
    time.sleep(1)
    m_list[fn] = "this is the first function"
    print 'func1: finishing'
    # return "func1"  # no need for return since Multiprocess doesnt return it =(

def func2(fn, m_list):
    print 'func2: starting'
    time.sleep(3)
    m_list[fn] = "this is function 2"
    print 'func2: finishing'
    # return "func2"

def func3(fn, m_list):
    print 'func3: starting'
    time.sleep(9)
    # if fail wont join the rest because it never populate the dict
    # or do a try/except to get something in return.
    raise ValueError("failed here")
    # if we want to get the error in the manager dict we can catch the error
    try:
        raise ValueError("failed here")
        m_list[fn] = "this is third"
    except:
        m_list[fn] = "this is third and it fail horrible"
        # print 'func3: finishing'
        # return "func3"


def runInParallel(*fns):  # * is to accept any input in list
    start_time = datetime.datetime.now()
    proc = []
    manager = multiprocessing.Manager()
    m_list = manager.dict()
    for fn in fns:
        # print fn
        # print dir(fn)
        p = Process(target=fn, name=fn.func_name, args=(fn, m_list))
        p.start()
        proc.append(p)
    for p in proc:
        p.join()  # 5 is the time out

    print datetime.datetime.now() - start_time
    return m_list, proc

if __name__ == '__main__':
    manager, proc = runInParallel(func1, func2, func3)
    # print dir(proc[0])
    # print proc[0]._name
    # print proc[0].name
    # print proc[0].exitcode

    # here you can check what did fail
    for i in proc:
        print i.name, i.exitcode  # name was set up in the Process line 53

    # here will only show the function that worked and where able to populate the 
    # manager dict
    for i, j in manager.items():
        print dir(i)  # things you can do to the function
        print i, j
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.