Que fait exactement la méthode .join () du module de multitraitement Python?

110

En savoir plus sur le multitraitement Python (à partir d'un article de PMOTW ) et j'aimerais avoir des éclaircissements sur ce que fait exactement la join()méthode.

Dans un ancien tutoriel de 2008, il indique que sans l' p.join()appel dans le code ci-dessous, "le processus enfant restera inactif et ne se terminera pas, devenant un zombie que vous devez tuer manuellement".

from multiprocessing import Process

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

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

J'ai ajouté une impression du PIDainsi qu'un time.sleeppour tester et pour autant que je sache, le processus se termine tout seul:

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

dans les 20 secondes:

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

après 20 secondes:

947 ttys001    0:00.13 -bash

Le comportement est le même avec l' p.join()ajout à la fin du fichier. Le module Python de la semaine offre une explication très lisible du module ; "Pour attendre qu'un processus ait terminé son travail et se soit arrêté, utilisez la méthode join ().", Mais il semble qu'au moins OS X le fasse de toute façon.

Je me demande aussi le nom de la méthode. La .join()méthode concatène-elle quelque chose ici? S'agit-il de concaténer un processus avec sa fin? Ou partage-t-il simplement un nom avec la .join()méthode native de Python ?

MikeiLL
la source
2
pour autant que je sache, il contient le thread principal et attend que le processus enfant se termine, puis rejoins les ressources dans le thread principal, effectue principalement une sortie propre.
abhishekgarg
ah cela a du sens. Donc, est-ce que les réels CPU, Memory resourcessont séparés du processus parent, puis sont joinà nouveau modifiés une fois le processus enfant terminé?
MikeiLL
oui, c'est ce qu'il fait. Donc, si vous ne les rejoignez pas, lorsque le processus enfant est terminé, il se trouve juste comme un processus défunt ou mort
abhishekgarg
@abhishekgarg Ce n'est pas vrai. Les processus enfants seront implicitement joints une fois le processus principal terminé.
dano
@dano, j'apprends aussi python et je viens de partager ce que j'ai trouvé dans mes tests.Dans mes tests, j'ai eu un processus principal sans fin, alors peut-être que c'est pour cela que j'ai vu ces processus enfants comme obsolètes.
abhishekgarg

Réponses:

125

La join()méthode, lorsqu'elle est utilisée avec threadingou multiprocessing, n'est pas liée à str.join()- elle ne concatène en fait rien. Au contraire, cela signifie simplement "attendez que ce [thread / processus] se termine". Le nom joinest utilisé car l' multiprocessingAPI du module est censée ressembler à l' threadingAPI du threadingmodule et que le module utilise joinpour son Threadobjet. Utiliser le terme joinpour signifier «attendre qu'un thread se termine» est courant dans de nombreux langages de programmation, donc Python vient de l'adopter également.

Maintenant, la raison pour laquelle vous voyez le délai de 20 secondes avec et sans l'appel à join()est que par défaut, lorsque le processus principal est prêt à quitter, il appellera implicitement join()toutes les multiprocessing.Processinstances en cours d'exécution . Ce n'est pas aussi clairement indiqué dans la multiprocessingdocumentation qu'il devrait l'être, mais il est mentionné dans la section Directives de programmation :

Souvenez-vous également que les processus non démoniaques seront automatiquement joints.

Vous pouvez remplacer ce comportement en définissant l' daemonindicateur sur le Processà Trueavant de démarrer le processus:

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

Si vous faites cela, le processus enfant sera terminé dès que le processus principal sera terminé :

démon

L'indicateur de démon du processus, une valeur booléenne. Ceci doit être défini avant l'appel de start ().

La valeur initiale est héritée du processus de création.

Lorsqu'un processus se termine, il tente de mettre fin à tous ses processus enfants démoniaques.

Dano
la source
6
J'avais compris que p.daemon=Truec'était pour "démarrer un processus d'arrière-plan qui s'exécute sans bloquer la sortie du programme principal". Mais si "Le processus démon se termine automatiquement avant que le programme principal ne se termine", à quoi sert-il exactement?
MikeiLL
8
@MikeiLL Fondamentalement, tout ce que vous voulez se passe en arrière-plan tant que le processus parent est en cours d'exécution, mais cela n'a pas besoin d'être nettoyé correctement avant de quitter le programme principal. Peut-être un processus de travail qui lit des données à partir d'un socket ou d'un périphérique matériel, et renvoie ces données au parent via une file d'attente ou les traite en arrière-plan dans un but précis? En général, je dirais que l'utilisation d'un daemonicprocessus enfant n'est pas très sûr, car le processus va se terminer sans permettre de nettoyer les ressources ouvertes qu'il peut avoir .. (suite).
dano
7
@MikeiLL Une meilleure pratique serait de signaler à l'enfant de nettoyer et de quitter avant de quitter le processus principal. Vous pourriez penser qu'il serait judicieux de laisser le processus enfant démoniaque en cours d'exécution lorsque le parent se termine, mais gardez à l'esprit que l' multiprocessingAPI est conçue pour imiter l' threadingAPI le plus fidèlement possible. Les threading.Threadobjets démoniaques sont arrêtés dès que le thread principal se termine, donc les multiprocesing.Processobjets démoniaques se comportent de la même manière.
dano
38

Sans le join(), le processus principal peut se terminer avant le processus enfant. Je ne sais pas dans quelles circonstances cela conduit au zombieisme.

L'objectif principal de join()est de s'assurer qu'un processus enfant est terminé avant que le processus principal ne fasse quoi que ce soit qui dépend du travail du processus enfant.

L'étymologie de join()est que c'est l'opposé de fork, qui est le terme courant dans les systèmes d'exploitation de la famille Unix pour créer des processus enfants. Un seul processus "fourche" en plusieurs, puis "rejoint" en un seul.

Russell Borogove
la source
2
Il utilise le nom join()car join()c'est ce qui est utilisé pour attendre la fin d'un threading.Threadobjet, et l' multiprocessingAPI est censée imiter l' threadingAPI autant que possible.
dano
Votre deuxième déclaration aborde le problème que je traite dans un projet en cours.
MikeiLL
Je comprends la partie où le thread principal attend que le sous-processus se termine, mais cela ne va-t-il pas à l'encontre de l'objectif de l'exécution asynchrone? N'est-il pas censé terminer l'exécution, indépendamment (la sous-tâche ou le processus)?
Apurva Kunkulol
1
@ApurvaKunkulol Dépend de la façon dont vous l'utilisez, mais join()est nécessaire dans le cas où le thread principal a besoin des résultats du travail des sous-threads. Par exemple, si vous effectuez le rendu de quelque chose et attribuez 1/4 de l'image finale à chacun des 4 sous-processus, et que vous souhaitez afficher l'image entière une fois l'opération terminée.
Russell Borogove
@RussellBorogove Ah! J'ai compris. Ensuite, la signification de l'activité asynchrone est un peu différente ici. Cela ne doit signifier que le fait que les sous-processus sont censés effectuer leurs tâches simultanément avec le thread principal tandis que le thread principal fait également son travail au lieu d'attendre les sous-processus.
Apurva Kunkulol
12

Je ne vais pas expliquer en détail ce que joinfait, mais voici l'étymologie et l'intuition derrière cela, ce qui devrait vous aider à vous souvenir plus facilement de sa signification.

L'idée est que l'exécution " fourche " en plusieurs processus dont l'un est le maître, les autres ouvriers (ou "esclaves"). Lorsque les nœuds de calcul ont terminé, ils «rejoignent» le maître afin que l'exécution en série puisse reprendre.

La joinméthode oblige le processus maître à attendre qu'un travailleur le rejoigne. La méthode aurait mieux été appelée "wait", puisque c'est le comportement réel qu'elle provoque dans le maître (et c'est ce qu'elle appelle dans POSIX, bien que les threads POSIX l'appellent aussi "join"). La jonction se produit uniquement comme un effet des fils qui coopèrent correctement, ce n'est pas quelque chose que le maître fait .

Les noms «fork» et «join» sont utilisés dans ce sens dans le multiprocessing depuis 1963 .

larsmans
la source
Donc, d'une certaine manière, cette utilisation du mot a joinpeut-être précédé son utilisation en référence à la concaténation, par opposition à l'inverse.
MikeiLL
1
Il est peu probable que l'utilisation en concaténation dérive de l'utilisation en multitraitement; au contraire, les deux sens dérivent séparément du sens anglais simple du mot.
Russell Borogove
2

join()est utilisé pour attendre la fermeture des processus de travail. Il faut appeler close()ou terminate()avant d'utiliser join().

Comme @Russell l'a mentionné, la jointure est comme l'opposé de fork (qui génère des sous-processus).

Pour que la jointure s'exécute, vous devez exécuter, close()ce qui empêchera toute autre tâche d'être soumise au pool et quittera une fois toutes les tâches terminées. Sinon, l'exécution se terminate()terminera simplement en arrêtant immédiatement tous les processus de travail.

"the child process will sit idle and not terminate, becoming a zombie you must manually kill" cela est possible lorsque le processus principal (parent) se termine mais que le processus enfant est toujours en cours d'exécution et qu'une fois terminé, il n'a pas de processus parent auquel retourner son état de sortie.

Ani Menon
la source
2

L' join()appel garantit que les lignes suivantes de votre code ne sont pas appelées avant que tous les processus de multitraitement ne soient terminés.

Par exemple, sans le join(), le code suivant sera appelé restart_program()avant même la fin des processus, ce qui est similaire à asynchrone et n'est pas ce que nous voulons (vous pouvez essayer):

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
la source
0

Pour attendre qu'un processus ait terminé son travail et quitte, utilisez la méthode join ().

et

Remarque Il est important de joindre () le processus après l'avoir terminé afin de donner à la machine d'arrière-plan le temps de mettre à jour le statut de l'objet pour refléter l'arrêt.

Ceci est un bon exemple m'a aidé à le comprendre: ici

Une chose que j'ai remarquée personnellement, c'est que mon processus principal a été mis en pause jusqu'à ce que l'enfant ait terminé son processus en utilisant la méthode join () qui a vaincu le point de moi d'utiliser multiprocessing.Process()en premier lieu.

Josh
la source