¿Qué hace exactamente el método .join () del módulo multiprocesamiento de Python?

110

Aprender sobre el multiprocesamiento de Python (de un artículo de PMOTW ) y me encantaría una aclaración sobre qué join()está haciendo exactamente el método.

En un viejo tutorial de 2008 , establece que sin la p.join()llamada en el código siguiente, "el proceso hijo permanecerá inactivo y no terminará, convirtiéndose en un zombi que debe matar manualmente".

from multiprocessing import Process

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

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

Agregué una copia impresa del PIDy una time.sleeppara probar y, por lo que puedo decir, el proceso termina por sí 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()

en 20 segundos:

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

después de 20 segundos:

947 ttys001    0:00.13 -bash

El comportamiento es el mismo con p.join()agregado al final del archivo. Python Module of the Week ofrece una explicación muy legible del módulo. ; "Para esperar hasta que un proceso haya completado su trabajo y salido, use el método join ()", pero parece que al menos OS X lo estaba haciendo de todos modos.

También me pregunto sobre el nombre del método. ¿El .join()método está concatenando algo aquí? ¿Está concatenando un proceso con su final? ¿O simplemente comparte un nombre con el .join()método nativo de Python ?

MikeiLL
fuente
2
Hasta donde yo sé, contiene el hilo principal y espera a que se complete el proceso secundario y luego vuelve a unir los recursos en el hilo principal, principalmente hace una salida limpia.
abhishekgarg
ah eso tiene sentido. Entonces, ¿lo real CPU, Memory resourcesse separa del proceso principal y luego se joinvuelve a editar una vez que se completa el proceso secundario?
MikeiLL
sí, eso es lo que está haciendo. Entonces, si no se une a ellos nuevamente, cuando el proceso hijo finaliza, simplemente se encuentra como un proceso difunto o muerto
abhishekgarg
@abhishekgarg Eso no es cierto. Los procesos secundarios se unirán implícitamente cuando se complete el proceso principal.
dano
@dano, también estoy aprendiendo Python y acabo de compartir lo que encontré en mis pruebas, en mis pruebas tuve un proceso principal interminable, así que tal vez por eso vi esos procesos secundarios como extintos.
abhishekgarg

Respuestas:

125

El join()método, cuando se usa con threadingo multiprocessing, no está relacionado con str.join(); en realidad, no está concatenando nada. Más bien, solo significa "esperar a que este [hilo / proceso] se complete". El nombre joinse usa porque la multiprocessingAPI del módulo está destinada a ser similar a la threadingAPI del módulo, y el threadingmódulo usa joinpara su Threadobjeto. Usar el término joinpara significar "esperar a que se complete un hilo" es común en muchos lenguajes de programación, por lo que Python también lo adoptó.

Ahora, la razón por la que ve el retraso de 20 segundos con y sin la llamada a join()es porque, de forma predeterminada, cuando el proceso principal está listo para salir, llamará implícitamente join()a todas las multiprocessing.Processinstancias en ejecución . Esto no está tan claramente establecido en los multiprocessingdocumentos como debería estar, pero se menciona en la sección Pautas de programación :

Recuerde también que los procesos no demoníacos se unirán automáticamente.

Puede anular este comportamiento estableciendo la daemonbandera en el Processque Trueantes de iniciar el proceso:

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

Si lo hace, el proceso hijo finalizará tan pronto como se complete el proceso principal :

demonio

La bandera del demonio del proceso, un valor booleano. Esto debe establecerse antes de llamar a start ().

El valor inicial se hereda del proceso de creación.

Cuando un proceso sale, intenta terminar todos sus procesos secundarios demoníacos.

dano
fuente
6
Comprendí que p.daemon=Trueera para "iniciar un proceso en segundo plano que se ejecuta sin bloquear la salida del programa principal". Pero si "El proceso del demonio se termina automáticamente antes de que salga el programa principal", ¿cuál es exactamente su uso?
MikeiLL
8
@MikeiLL Básicamente, cualquier cosa que desee que suceda en segundo plano mientras se esté ejecutando el proceso principal, pero eso no necesita limpiarse con elegancia antes de salir del programa principal. ¿Quizás un proceso de trabajo que lee datos de un socket o dispositivo de hardware y envía esos datos al padre a través de una cola o los procesa en segundo plano con algún propósito? En general, diría que usar un daemonicproceso hijo no es muy seguro, porque el proceso se terminará sin permitir la limpieza de los recursos abiertos que pueda tener ... (cont).
dano
7
@MikeiLL Una mejor práctica sería indicarle al niño que se limpie y salga antes de salir del proceso principal. Podría pensar que tendría sentido dejar el proceso hijo demoníaco ejecutándose cuando el padre sale, pero tenga en cuenta que la multiprocessingAPI está diseñada para imitar la threadingAPI lo más fielmente posible. Los threading.Threadobjetos daemonic se terminan tan pronto como sale del hilo principal, por lo que los multiprocesing.Processobjetos daemonic se comportan de la misma manera.
dano
38

Sin el join() , el proceso principal puede completarse antes que el proceso hijo. No estoy seguro de en qué circunstancias eso conduce al zombieismo.

El objetivo principal de join()es garantizar que un proceso hijo se haya completado antes de que el proceso principal haga algo que dependa del trabajo del proceso hijo.

La etimología de join()es que es lo opuesto a fork, que es el término común en los sistemas operativos de la familia Unix para crear procesos secundarios. Un solo proceso se "bifurca" en varios y luego se "une" nuevamente en uno.

Russell Borogove
fuente
2
Utiliza el nombre join()porque join()es lo que se utiliza para esperar threading.Threada que se complete un objeto, y la multiprocessingAPI está destinada a imitar la threadingAPI tanto como sea posible.
dano
Su segunda declaración aborda el problema con el que estoy lidiando en un proyecto actual.
MikeiLL
Entiendo la parte en la que el hilo principal espera a que se complete el subproceso, pero ¿eso no frustra el propósito de la ejecución asincrónica? ¿No se supone que debe terminar la ejecución, de forma independiente (la subtarea o proceso)?
Apurva Kunkulol
1
@ApurvaKunkulol Depende de cómo lo uses, pero join()es necesario en el caso de que el hilo principal necesite los resultados del trabajo de los subprocesos. Por ejemplo, si está renderizando algo y asigna 1/4 de la imagen final a cada uno de los 4 subprocesos, y desea mostrar la imagen completa cuando esté lista.
Russell Borogove
@RussellBorogove ¡Ah! Lo entiendo. Entonces, el significado de actividad asincrónica es un poco diferente aquí. Debe significar solo el hecho de que los subprocesos están destinados a llevar a cabo sus tareas simultáneamente con el subproceso principal, mientras que el subproceso principal también hace su trabajo en lugar de esperar ociosamente los subprocesos.
Apurva Kunkulol
12

No voy a explicar en detalle qué joines lo que hace, pero aquí está la etimología y la intuición detrás de él, que debería ayudarlo a recordar su significado más fácilmente.

La idea es que la ejecución se " bifurca " en múltiples procesos de los cuales uno es el maestro, el resto trabajadores (o "esclavos"). Cuando los trabajadores terminan, se "unen" al maestro para que se pueda reanudar la ejecución en serie.

El joinmétodo hace que el proceso maestro espere a que un trabajador se una a él. El método podría haber sido mejor llamado "esperar", ya que ese es el comportamiento real que causa en el maestro (y así es como se llama en POSIX, aunque los hilos de POSIX también lo llaman "unirse"). La unión solo ocurre como un efecto de la cooperación adecuada de los hilos, no es algo que haga el maestro .

Los nombres "fork" y "join" se han utilizado con este significado en multiprocesamiento desde 1963 .

larsmans
fuente
Entonces, en cierto modo, este uso de la palabra joinpuede haber precedido a su uso para referirse a la concatenación, a diferencia de lo contrario.
MikeiLL
1
Es poco probable que el uso en concatenación se derive del uso en multiprocesamiento; más bien, ambos sentidos se derivan por separado del sentido de la palabra en inglés simple.
Russell Borogove
2

join()se utiliza para esperar a que finalicen los procesos de trabajo. Hay que llamar close()o terminate()antes de usar join().

Como mencionó @Russell, join es como lo opuesto a fork (que genera subprocesos).

Para que la unión se ejecute, debe ejecutar, close()lo que evitará que se envíen más tareas al grupo y saldrá una vez que se completen todas las tareas. Alternativamente, la ejecución terminate()simplemente terminará deteniendo todos los procesos de trabajo inmediatamente.

"the child process will sit idle and not terminate, becoming a zombie you must manually kill" esto es posible cuando el proceso principal (padre) sale pero el proceso hijo todavía se está ejecutando y, una vez completado, no tiene ningún proceso padre al que devolver su estado de salida.

Ani Menon
fuente
2

los join() llamada asegura que las líneas subsiguientes de su código no sean llamadas antes de que se completen todos los procesos de multiprocesamiento.

Por ejemplo, sin el join(), el siguiente código llamará restart_program()incluso antes de que finalicen los procesos, que es similar al asincrónico y no es lo que queremos (puedes probar):

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()
Yi Xiang Chong
fuente
0

Para esperar hasta que un proceso haya completado su trabajo y haya salido, use el método join ().

y

Nota Es importante unir () el proceso después de terminarlo para darle tiempo a la maquinaria de fondo para actualizar el estado del objeto para reflejar la terminación.

Este es un buen ejemplo que me ayudó a entenderlo: aquí

Una cosa que noté personalmente fue que mi proceso principal se detuvo hasta que el niño terminó su proceso usando el método join () que derrotó el punto de que yo lo usaba multiprocessing.Process()en primer lugar.

Josh
fuente