Was genau macht die .join () -Methode des Python-Multiprocessing-Moduls?

110

Erfahren Sie mehr über Python Multiprocessing (aus einem PMOTW-Artikel ) und würden Sie gerne klarstellen, was genau die join()Methode tut.

In einem alten Tutorial aus dem Jahr 2008 heißt es, dass ohne den p.join()Aufruf im folgenden Code "der untergeordnete Prozess im Leerlauf bleibt und nicht beendet wird und zu einem Zombie wird, den Sie manuell töten müssen".

from multiprocessing import Process

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

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

Ich habe einen Ausdruck von PIDsowie einen time.sleepzu testenden hinzugefügt und soweit ich das beurteilen kann, endet der Prozess von selbst:

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

innerhalb von 20 Sekunden:

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

nach 20 Sekunden:

947 ttys001    0:00.13 -bash

Das Verhalten ist dasselbe, p.join()wenn am Ende der Datei wieder hinzugefügt wird. Das Python-Modul der Woche bietet eine gut lesbare Erklärung des Moduls . "Um zu warten, bis ein Prozess seine Arbeit abgeschlossen und beendet hat, verwenden Sie die join () -Methode.", Aber es scheint, dass zumindest OS X dies trotzdem getan hat.

Ich wundere mich auch über den Namen der Methode. .join()Verkettet die Methode hier etwas? Verkettet es einen Prozess mit seinem Ende? Oder teilt es nur einen Namen mit Pythons nativer .join()Methode?

MikeiLL
quelle
2
Soweit ich weiß, enthält es den Hauptthread und wartet, bis der untergeordnete Prozess abgeschlossen ist. Anschließend werden die Ressourcen im Hauptthread wieder zusammengefügt. Meistens wird ein sauberer Exit ausgeführt.
Abhishekgarg
Ah, das macht Sinn. Werden also die tatsächlichen CPU, Memory resourcesvom übergeordneten Prozess getrennt joinund nach Abschluss des untergeordneten Prozesses wieder zurückgesetzt?
MikeiLL
Ja, das ist es, was es tut. Also, wenn Sie sich ihnen nicht wieder anschließen, wenn der
abhishekgarg
@abhishekgarg Das stimmt nicht. Die untergeordneten Prozesse werden implizit verbunden, wenn der Hauptprozess abgeschlossen ist.
Dano
@dano, ich lerne auch Python und habe gerade geteilt, was ich in meinen Tests gefunden habe. In meinen Tests hatte ich einen nie endenden Hauptprozess. Vielleicht deshalb habe ich diese untergeordneten Prozesse als nicht mehr funktionierend angesehen.
Abhishekgarg

Antworten:

125

Das join()Verfahren, wenn verwendet mit threadingoder multiprocessingwird nicht im Zusammenhang mit str.join()- es ist nicht wirklich etwas verketten zusammen. Es bedeutet vielmehr nur "Warten Sie, bis dieser [Thread / Prozess] abgeschlossen ist". Der Name joinwird verwendet, da die multiprocessingAPI des threadingModuls der API des threadingModuls ähneln soll und das Modul joinfür sein ThreadObjekt verwendet. Die Verwendung des Begriffs join"Warten auf den Abschluss eines Threads" ist in vielen Programmiersprachen üblich, daher hat Python ihn auch übernommen.

Der Grund für die Verzögerung von 20 Sekunden mit und ohne Aufruf von join()ist, dass der Hauptprozess standardmäßig join()alle laufenden multiprocessing.ProcessInstanzen aufruft, wenn er zum Beenden bereit ist . Dies ist in den multiprocessingDokumenten nicht so klar angegeben, wie es sein sollte, wird jedoch im Abschnitt Programmierrichtlinien erwähnt :

Denken Sie auch daran, dass nicht-dämonische Prozesse automatisch verbunden werden.

Sie können dieses Verhalten überschreiben, indem Sie vor dem Starten des Prozesses das daemonFlag auf Processto setzen True:

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

Wenn Sie dies tun, wird der untergeordnete Prozess beendet, sobald der Hauptprozess abgeschlossen ist :

Daemon

Das Daemon-Flag des Prozesses, ein boolescher Wert. Dies muss eingestellt werden, bevor start () aufgerufen wird.

Der Anfangswert wird vom Erstellungsprozess geerbt.

Wenn ein Prozess beendet wird, versucht er, alle seine dämonischen untergeordneten Prozesse zu beenden.

dano
quelle
6
Ich habe verstanden, dass dies dazu p.daemon=Truediente, "einen Hintergrundprozess zu starten, der ausgeführt wird, ohne das Beenden des Hauptprogramms zu blockieren". Aber wenn "Der Dämonprozess wird automatisch beendet, bevor das Hauptprogramm beendet wird", was genau wird er verwendet?
MikeiLL
8
@MikeiLL Grundsätzlich alles, was Sie im Hintergrund tun möchten, solange der übergeordnete Prozess ausgeführt wird. Dies muss jedoch vor dem Beenden des Hauptprogramms nicht ordnungsgemäß bereinigt werden. Vielleicht ein Arbeitsprozess, der Daten von einem Socket oder Hardwaregerät liest und diese Daten über eine Warteschlange an das übergeordnete Element zurückgibt oder sie für einen bestimmten Zweck im Hintergrund verarbeitet? Im Allgemeinen würde ich sagen, dass die Verwendung eines daemonicuntergeordneten Prozesses nicht sehr sicher ist, da der Prozess beendet wird, ohne dass offene Ressourcen bereinigt werden müssen. (Forts.)
Dano
7
@MikeiLL Eine bessere Vorgehensweise wäre, dem Kind zu signalisieren, dass es vor dem Beenden des Hauptprozesses aufräumen und beenden soll. Sie könnten denken, dass es sinnvoll wäre, den dämonischen untergeordneten Prozess beim Beenden des übergeordneten Prozesses laufen zu lassen, aber denken Sie daran, dass die multiprocessingAPI so konzipiert ist, dass sie die threadingAPI so genau wie möglich nachahmt . Dämonische threading.ThreadObjekte werden beendet, sobald der Haupt-Thread beendet wird, sodass sich dämonische multiprocesing.ProcessObjekte genauso verhalten.
Dano
38

Ohne das join()kann der Hauptprozess abgeschlossen werden, bevor der untergeordnete Prozess dies tut. Ich bin mir nicht sicher, unter welchen Umständen das zu Zombieismus führt.

Der Hauptzweck von join()besteht darin, sicherzustellen, dass ein untergeordneter Prozess abgeschlossen ist, bevor der Hauptprozess etwas ausführt, das von der Arbeit des untergeordneten Prozesses abhängt.

Die Etymologie von join()ist, dass es das Gegenteil von ist fork, was der übliche Begriff in Betriebssystemen der Unix-Familie zum Erstellen untergeordneter Prozesse ist. Ein einzelner Prozess "teilt" sich in mehrere und "verbindet" sich dann wieder zu einem.

Russell Borogove
quelle
2
Der Name wird verwendet, join()weil join()damit gewartet wird, bis ein threading.ThreadObjekt abgeschlossen ist, und die multiprocessingAPI soll die threadingAPI so weit wie möglich nachahmen .
Dano
Ihre zweite Aussage befasst sich mit dem Problem, mit dem ich mich in einem aktuellen Projekt befasse.
MikeiLL
Ich verstehe den Teil, in dem der Hauptthread auf den Abschluss des Unterprozesses wartet, aber besiegt diese Art nicht den Zweck der asynchronen Ausführung? Soll die Ausführung nicht unabhängig (die Unteraufgabe oder der Prozess) beendet werden?
Apurva Kunkulol
1
@ApurvaKunkulol Hängt davon ab, wie Sie es verwenden, wird jedoch join()in dem Fall benötigt, in dem der Hauptthread die Ergebnisse der Arbeit der Unter-Threads benötigt. Wenn Sie beispielsweise etwas rendern und jedem der vier Unterprozesse 1/4 des endgültigen Bilds zuweisen und das gesamte Bild anzeigen möchten, wenn es fertig ist.
Russell Borogove
@ RussellBorogove Ah! Ich verstehe es. Dann ist die Bedeutung der asynchronen Aktivität hier etwas anders. Es muss nur die Tatsache bedeuten, dass die Unterprozesse ihre Aufgaben gleichzeitig mit dem Hauptthread ausführen sollen, während der Hauptthread auch seine Arbeit erledigt, anstatt nur untätig auf die Unterprozesse zu warten.
Apurva Kunkulol
12

Ich werde nicht im Detail erklären, was jointut, aber hier ist die Etymologie und die Intuition dahinter, die Ihnen helfen sollen, sich leichter an ihre Bedeutung zu erinnern.

Die Idee ist, dass die Ausführung in mehrere Prozesse " zerfällt ", von denen einer der Master, die restlichen Arbeiter (oder "Slaves") ist. Wenn die Arbeiter fertig sind, "treten" sie dem Master bei, damit die serielle Ausführung fortgesetzt werden kann.

Die joinMethode bewirkt, dass der Master-Prozess darauf wartet, dass ein Mitarbeiter ihm beitritt. Die Methode könnte besser als "Warten" bezeichnet worden sein, da dies das tatsächliche Verhalten ist, das sie im Master verursacht (und so wird sie in POSIX genannt, obwohl POSIX-Threads sie auch "Join" nennen). Das Verbinden erfolgt nur, wenn die Threads ordnungsgemäß zusammenarbeiten. Dies ist nicht etwas, was der Master tut .

Die Namen "Fork" und "Join" werden seit 1963 in der Mehrfachverarbeitung mit dieser Bedeutung verwendet .

Larsmans
quelle
In gewisser Weise könnte diese Verwendung des Wortes joinseiner Verwendung in Bezug auf die Verkettung vorausgegangen sein, im Gegensatz zur Umkehrung.
MikeiLL
1
Es ist unwahrscheinlich, dass die Verwendung in der Verkettung von der Verwendung in der Mehrfachverarbeitung abgeleitet wird. vielmehr leiten sich beide Sinne getrennt vom einfachen englischen Sinn des Wortes ab.
Russell Borogove
2

join()wird verwendet, um auf das Beenden der Worker-Prozesse zu warten. Man muss anrufen close()oder terminate()vor der Verwendung join().

Wie bei @Russell erwähnt , ist Join wie das Gegenteil von Fork (das Subprozesse von Spawns erzeugt).

Damit Join beitreten kann, müssen Sie ausführen, um zu close()verhindern, dass weitere Aufgaben an den Pool gesendet werden, und um zu beenden, sobald alle Aufgaben abgeschlossen sind. Alternativ wird das Ausführen terminate()einfach beendet, indem alle Arbeitsprozesse sofort gestoppt werden.

"the child process will sit idle and not terminate, becoming a zombie you must manually kill" Dies ist möglich, wenn der Hauptprozess (übergeordneter Prozess) beendet wird, der untergeordnete Prozess jedoch noch ausgeführt wird und nach Abschluss kein übergeordneter Prozess mehr vorhanden ist, auf den der Beendigungsstatus zurückgesetzt werden kann.

Ani Menon
quelle
2

Der join()Aufruf stellt sicher, dass nachfolgende Zeilen Ihres Codes nicht aufgerufen werden, bevor alle Mehrfachverarbeitungsprozesse abgeschlossen sind.

Ohne das join()wird der folgende Code beispielsweise restart_program()noch vor Abschluss der Prozesse aufgerufen. Dies ähnelt asynchron und ist nicht das, was wir wollen (Sie können es versuchen):

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
quelle
0

Verwenden Sie die Methode join (), um zu warten, bis ein Prozess seine Arbeit abgeschlossen und beendet hat.

und

Hinweis Es ist wichtig, dem Prozess nach dem Beenden beizutreten (), damit die Hintergrundmaschine Zeit hat, den Status des Objekts zu aktualisieren, um die Beendigung widerzuspiegeln.

Dies ist ein gutes Beispiel, das mir geholfen hat, es zu verstehen: hier

Eine Sache, die mir persönlich aufgefallen ist, war, dass mein Hauptprozess angehalten wurde, bis das Kind seinen Prozess mit der join () -Methode beendet hatte, die den Punkt, den ich überhaupt benutzte, besiegte multiprocessing.Process().

Josh
quelle