Che cosa fa esattamente il metodo .join () del modulo multiprocessing Python?


110

Imparando a conoscere il multiprocessing di Python (da un articolo di PMOTW ) e mi piacerebbe qualche chiarimento su cosa join()sta facendo esattamente il metodo.

In un vecchio tutorial del 2008 si afferma che senza la p.join()chiamata nel codice seguente, "il processo figlio resterà inattivo e non terminerà, diventando uno zombi che devi uccidere manualmente".

from multiprocessing import Process

def say_hello(name='world'):
    print "Hello, %s" % name

p = Process(target=say_hello)
p.start()
p.join()

Ho aggiunto una stampa del PIDcosì come un time.sleeptest e per quanto ne so, il processo termina da solo:

from multiprocessing import Process
import sys
import time

def say_hello(name='world'):
    print "Hello, %s" % name
    print 'Starting:', p.name, p.pid
    sys.stdout.flush()
    print 'Exiting :', p.name, p.pid
    sys.stdout.flush()
    time.sleep(20)

p = Process(target=say_hello)
p.start()
# no p.join()

entro 20 secondi:

936 ttys000    0:00.05 /Library/Frameworks/Python.framework/Versions/2.7/Reso
938 ttys000    0:00.00 /Library/Frameworks/Python.framework/Versions/2.7/Reso
947 ttys001    0:00.13 -bash

dopo 20 secondi:

947 ttys001    0:00.13 -bash

Il comportamento è lo stesso con l' p.join()aggiunta di nuovo alla fine del file. Python Module of the Week offre una spiegazione molto leggibile del modulo ; "Per aspettare che un processo abbia completato il suo lavoro ed è uscito, usa il metodo join ().", Ma sembra che almeno OS X lo stesse facendo comunque.

Mi chiedo anche il nome del metodo. Il .join()metodo concatena qualcosa qui? Sta concatenando un processo con la sua fine? O condivide semplicemente un nome con il .join()metodo nativo di Python ?


2
per quanto ne so, tiene il thread principale e attende il completamento del processo figlio e quindi unire nuovamente le risorse nel thread principale, per lo più esegue un'uscita pulita.
abhishekgarg

ah questo ha un senso. Quindi l'effettivo CPU, Memory resourcesviene separato dal processo genitore, quindi joinreinserito di nuovo dopo che il processo figlio è stato completato?
MikeiLL

sì, è quello che sta facendo. Quindi, se non ti unisci a loro, quando il processo figlio è finito, giace semplicemente come un processo defunto o morto
abhishekgarg

@abhishekgarg Non è vero. I processi figlio verranno uniti in modo implicito al termine del processo principale.
dano

@dano, sto anche imparando python e ho appena condiviso ciò che ho trovato nei miei test, nei miei test ho avuto un processo principale senza fine, quindi forse è per questo che ho visto quei processi figlio come defunti.
abhishekgarg

Risposte:


125

Il join()metodo, se utilizzato con threadingo multiprocessing, non è correlato a str.join(): in realtà non concatena nulla insieme. Piuttosto, significa semplicemente "attendere il completamento di questo [thread / processo]". Il nome joinviene utilizzato perché l' multiprocessingAPI del modulo deve essere simile threadingall'API del threadingmodulo e il modulo utilizza joinper il suo Threadoggetto. Usare il termine joinper indicare "attendere il completamento di un thread" è comune in molti linguaggi di programmazione, quindi anche Python lo ha adottato.

Ora, il motivo per cui vedi il ritardo di 20 secondi sia con che senza la chiamata a join()è perché per impostazione predefinita, quando il processo principale è pronto per uscire, chiamerà implicitamente join()tutte le multiprocessing.Processistanze in esecuzione . Questo non è così chiaramente indicato nei multiprocessingdocumenti come dovrebbe essere, ma è menzionato nella sezione Linee guida di programmazione :

Ricorda anche che i processi non demoniaci verranno automaticamente uniti.

È possibile ignorare questo comportamento impostando la daemonbandiera sul Processa Trueprima di iniziare il processo:

p = Process(target=say_hello)
p.daemon = True
p.start()
# Both parent and child will exit here, since the main process has completed.

Se lo fai, il processo figlio verrà terminato non appena il processo principale sarà completato :

demone

Flag del demone del processo, un valore booleano. Deve essere impostato prima che venga chiamato start ().

Il valore iniziale viene ereditato dal processo di creazione.

Quando un processo termina, tenta di terminare tutti i suoi processi figlio demoniaci.


6
Capivo che p.daemon=Trueera per "avviare un processo in background che viene eseguito senza bloccare l'uscita del programma principale". Ma se "Il processo del demone viene terminato automaticamente prima che il programma principale esca", qual è esattamente il suo utilizzo?
MikeiLL

8
@ MikeiLL Fondamentalmente tutto ciò che si desidera venga eseguito in background per tutto il tempo in cui il processo principale è in esecuzione, ma non è necessario ripulirlo con grazia prima di uscire dal programma principale. Forse un processo di lavoro che legge i dati da un socket o da un dispositivo hardware e li restituisce al genitore tramite una coda o li elabora in background per qualche scopo? In generale, direi che l'uso di un daemonicprocesso figlio non è molto sicuro, perché il processo verrà terminato senza consentire la pulizia delle risorse aperte che potrebbe avere .. (segue).
dano

7
@MikeiLL Una pratica migliore sarebbe segnalare al bambino di pulire e uscire prima di uscire dal processo principale. Potresti pensare che avrebbe senso lasciare il processo figlio demonico in esecuzione quando il genitore esce, ma tieni presente che l' multiprocessingAPI è progettata per imitare l' threadingAPI il più fedelmente possibile. Gli threading.Threadoggetti demoniaci vengono terminati non appena il thread principale esce, quindi gli multiprocesing.Processoggetti demonici si comportano allo stesso modo.
dano

38

Senza join(), il processo principale può essere completato prima del processo figlio. Non sono sicuro in quali circostanze ciò porti allo zombi.

Lo scopo principale di join()è garantire che un processo figlio sia stato completato prima che il processo principale esegua tutto ciò che dipende dal lavoro del processo figlio.

L'etimologia di join()è che è l'opposto di fork, che è il termine comune nei sistemi operativi della famiglia Unix per la creazione di processi figli. Un singolo processo "si divide" in diversi, quindi "si unisce" di nuovo in uno.


2
Utilizza il nome join()perché join()è ciò che viene utilizzato per attendere il completamento di un threading.Threadoggetto e l' multiprocessingAPI ha lo scopo di imitare l' threadingAPI il più possibile.
dano

La tua seconda affermazione affronta il problema che sto affrontando in un progetto in corso.
MikeiLL

Capisco la parte in cui il thread principale attende il completamento del sottoprocesso, ma questo tipo di non vanifica lo scopo dell'esecuzione asincrona? Non dovrebbe terminare l'esecuzione, in modo indipendente (sotto-attività o processo)?
Apurva Kunkulol

1
@ApurvaKunkulol Dipende da come lo stai usando, ma join()è necessario nel caso in cui il thread principale abbia bisogno dei risultati del lavoro dei sotto-thread. Ad esempio, se stai eseguendo il rendering di qualcosa e assegni 1/4 dell'immagine finale a ciascuno dei 4 sottoprocessi e desideri visualizzare l'intera immagine al termine.
Russell Borogove il

@RussellBorogove Ah! Capisco. Quindi il significato dell'attività asincrona è leggermente diverso qui. Deve significare solo il fatto che i sottoprocessi hanno lo scopo di svolgere le loro attività simultaneamente con il thread principale mentre anche il thread principale fa il suo lavoro invece di aspettare pigramente i sottoprocessi.
Apurva Kunkulol

12

Non spiegherò in dettaglio cosa joinfa, ma ecco l'etimologia e l'intuizione dietro, che dovrebbe aiutarti a ricordare più facilmente il suo significato.

L'idea è che l'esecuzione "si biforchi " in più processi di cui uno è il padrone, gli altri lavoratori (o "schiavi"). Quando i lavoratori hanno finito, "si uniscono" al master in modo che l'esecuzione seriale possa essere ripresa.

Il joinmetodo fa in modo che il processo master attenda che un lavoratore vi si unisca. Il metodo avrebbe potuto essere chiamato meglio "wait", poiché questo è il comportamento effettivo che provoca nel master (ed è quello che viene chiamato in POSIX, sebbene i thread POSIX lo chiamino anche "join"). L'unica che unisce si verifica per effetto dei fili cooperanti correttamente, non è qualcosa che il maestro fa .

I nomi "fork" e "join" sono stati usati con questo significato nel multiprocessing dal 1963 .


Quindi, in un certo senso, questo uso della parola joinpotrebbe aver preceduto il suo uso in riferimento alla concatenazione, al contrario del contrario.
MikeiLL

1
È improbabile che l'uso in concatenazione derivi dall'uso in multiprocessing; piuttosto entrambi i sensi derivano separatamente dal semplice senso inglese della parola.
Russell Borogove

2

join()viene utilizzato per attendere l'uscita dei processi di lavoro. Uno deve chiamare close()o terminate()prima di utilizzare join().

Come @Russell menzionato join è come l'opposto di fork (che genera sottoprocessi).

Affinché il join venga eseguito, è necessario eseguire il close()che impedirà l'invio di ulteriori attività al pool e l'uscita una volta completate tutte le attività. In alternativa, l'esecuzione terminate()terminerà semplicemente arrestando immediatamente tutti i processi di lavoro.

"the child process will sit idle and not terminate, becoming a zombie you must manually kill" questo è possibile quando il processo principale (genitore) esce ma il processo figlio è ancora in esecuzione e una volta completato non ha alcun processo genitore a cui restituire lo stato di uscita.


2

La join()chiamata garantisce che le righe successive del codice non vengano chiamate prima del completamento di tutti i processi multiprocessing.

Ad esempio, senza il join(), il codice seguente verrà chiamato restart_program()anche prima che i processi finiscano, che è simile a asincrono e non è quello che vogliamo (puoi provare):

num_processes = 5

for i in range(num_processes):
    p = multiprocessing.Process(target=calculate_stuff, args=(i,))
    p.start()
    processes.append(p)
for p in processes:
    p.join() # call to ensure subsequent line (e.g. restart_program) 
             # is not called until all processes finish

restart_program()

0

Per aspettare che un processo abbia completato il suo lavoro e sia uscito, usa il metodo join ().

e

Nota È importante join () il processo dopo averlo terminato per dare al macchinario in background il tempo di aggiornare lo stato dell'oggetto in modo da riflettere la terminazione.

Questo è un buon esempio mi ha aiutato a capirlo: qui

Una cosa che ho notato personalmente è che il mio processo principale è stato messo in pausa fino a quando il bambino non ha terminato il suo processo utilizzando il metodo join () che ha sconfitto il punto di me usando multiprocessing.Process()in primo luogo.

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.