Co dokładnie robi metoda wieloprocesorowa modułu .join () Pythona?

110

Poznanie wieloprocesorowości w Pythonie (z artykułu PMOTW ) i chciałbym uzyskać wyjaśnienie, co dokładnie join()robi ta metoda.

W starym samouczku z 2008 roku stwierdza się, że bez p.join()wywołania w poniższym kodzie „proces potomny będzie siedział bezczynnie i nie zostanie zakończony, stając się zombie, którego należy ręcznie zabić”.

from multiprocessing import Process

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

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

Dodałem wydruk PID, a także time.sleepdo badania i ile mogę powiedzieć tego, zakończone jest proces na własną rękę:

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

w ciągu 20 sekund:

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

po 20 sekundach:

947 ttys001    0:00.13 -bash

Zachowanie jest takie samo w przypadku p.join()dodania z powrotem na końcu pliku. Python Module of the Week oferuje bardzo czytelne wyjaśnienie modułu ; „Aby poczekać, aż proces zakończy swoją pracę i zakończy pracę, użyj metody join ().”, Ale wygląda na to, że przynajmniej OS X to robił.

Zastanawiam się też nad nazwą metody. Czy .join()metoda coś tutaj łączy? Czy łączy proces z jego zakończeniem? A może po prostu ma taką samą nazwę jak natywna .join()metoda Pythona ?

MikeiLL
źródło
2
o ile wiem, przechowuje główny wątek i czeka na zakończenie procesu potomnego, a następnie łączy zasoby w głównym wątku, w większości robi to czyste wyjście.
abhishekgarg
ach, to ma sens. Czy więc rzeczywiste CPU, Memory resourcessą oddzielane od procesu nadrzędnego, a następnie joinponownie odsyłane po zakończeniu procesu potomnego?
MikeiLL
tak, właśnie to robi. Tak więc, jeśli nie dołączysz do nich z powrotem, kiedy proces potomny zostanie zakończony, będzie to po prostu nieistniejący lub martwy proces
abhishekgarg
@abhishekgarg To nieprawda. Procesy potomne zostaną niejawnie połączone po zakończeniu procesu głównego.
dano
@dano, uczę się też Pythona i właśnie podzieliłem się tym, co znalazłem w moich testach, w moich testach miałem niekończący się główny proces, więc może dlatego uważałem te procesy potomne za niedziałające.
abhishekgarg

Odpowiedzi:

125

join()Metoda, gdy używana z threadinglub multiprocessingnie jest związane str.join()- to nie jest faktycznie złączenie wszystko razem. Oznacza raczej po prostu „poczekaj na zakończenie tego [wątku / procesu]”. Nazwa joinjest używana, ponieważ multiprocessinginterfejs API modułu ma wyglądać podobnie do threadinginterfejsu API modułu, a threadingmoduł używa joindla swojego Threadobiektu. Używając terminujoin oznaczającego „czekanie na zakończenie wątku” jest powszechne w wielu językach programowania, więc Python również go przyjął.

Powodem, dla którego widzisz 20-sekundowe opóźnienie zarówno z wywołaniem, jak i bez niego, join()jest to, że domyślnie, gdy proces główny jest gotowy do zakończenia, niejawnie wywoła join()wszystkie uruchomione multiprocessing.Processinstancje. W dokumentach nie jest to tak jasno określone, multiprocessingjak powinno, ale zostało to wspomniane w sekcji Wskazówki dotyczące programowania :

Pamiętaj również, że procesy inne niż demoniczne zostaną automatycznie połączone.

Możesz zmienić to zachowanie, ustawiając daemonflagę na Processto Trueprzed rozpoczęciem procesu:

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

Jeśli to zrobisz, proces potomny zostanie zakończony, gdy tylko zakończy się proces główny :

demon

Flaga demona procesu, wartość logiczna. Należy to ustawić przed wywołaniem metody start ().

Wartość początkowa jest dziedziczona z procesu tworzenia.

Kiedy proces kończy pracę, próbuje zakończyć wszystkie swoje demoniczne procesy potomne.

dano
źródło
6
Rozumiałem, że p.daemon=Truechodziło o „rozpoczęcie procesu w tle, który działa bez blokowania wyjścia programu głównego”. Ale jeśli „Proces demona kończy się automatycznie przed zakończeniem programu głównego”, to do czego dokładnie służy?
MikeiLL
8
@MikeiLL W zasadzie wszystko, co chcesz, aby działo się w tle tak długo, jak działa proces nadrzędny, ale nie trzeba tego starannie czyścić przed wyjściem z głównego programu. Być może proces roboczy, który odczytuje dane z gniazda lub urządzenia sprzętowego i przekazuje je z powrotem do rodzica za pośrednictwem kolejki lub przetwarza je w tle w jakimś celu? Generalnie powiedziałbym, że używanie daemonicprocesu potomnego nie jest zbyt bezpieczne, ponieważ proces zostanie zakończony bez umożliwienia wyczyszczenia wszelkich otwartych zasobów, które może posiadać ... (cd.).
dano,
7
@MikeiLL Lepszą praktyką byłoby zasygnalizowanie dziecku, aby posprzątało i zakończyło pracę przed zakończeniem głównego procesu. Można by pomyśleć, że sensowne byłoby pozostawienie uruchomionego demonicznego procesu potomnego po zakończeniu pracy rodzica, ale należy pamiętać, że multiprocessinginterfejs API został zaprojektowany tak, aby threadingjak najdokładniej naśladować interfejs API. threading.ThreadObiekty demoniczne są przerywane, gdy tylko główny wątek kończy działanie, więc multiprocesing.Processobiekty demoniczne zachowują się w ten sam sposób.
dano
38

Bez tego join()główny proces może zakończyć się przed procesem potomnym. Nie jestem pewien, w jakich okolicznościach prowadzi to do zombieizmu.

Głównym celem join()jest zapewnienie, że proces podrzędny został zakończony, zanim proces główny zrobi cokolwiek, co zależy od pracy procesu podrzędnego.

Etymologia join()jest taka, że ​​jest przeciwieństwem fork, co jest powszechnym terminem w systemach operacyjnych z rodziny Unix do tworzenia procesów potomnych. Pojedynczy proces „dzieli się” na kilka, a następnie „łączy” z powrotem w jeden.

Russell Borogove
źródło
2
Używa nazwy, join()ponieważ join()jest to, co było używane do oczekiwania na threading.Threadukończenie obiektu, a multiprocessingAPI ma naśladować threadingAPI w jak największym stopniu.
dano
Twoje drugie stwierdzenie dotyczy problemu, z którym mam do czynienia w obecnym projekcie.
MikeiLL
Rozumiem część, w której główny wątek czeka na zakończenie podprocesu, ale czy nie jest to sprzeczne z celem wykonania asynchronicznego? Czy nie ma kończyć wykonywania samodzielnie (pod-zadania lub procesu)?
Apurva Kunkulol
1
@ApurvaKunkulol Zależy od tego, jak go używasz, ale join()jest potrzebny w przypadku, gdy główny wątek potrzebuje wyników pracy pod-wątków. Na przykład, jeśli renderujesz coś i przypisujesz 1/4 końcowego obrazu do każdego z 4 podprocesów, a po zakończeniu chcesz wyświetlić cały obraz.
Russell Borogove
@RussellBorogove Ah! Rozumiem. Wtedy znaczenie aktywności asynchronicznej jest tutaj trochę inne. Musi to oznaczać tylko fakt, że podprocesy mają wykonywać swoje zadania jednocześnie z głównym wątkiem, podczas gdy główny wątek również wykonuje swoją pracę, zamiast po prostu bezczynnie czekać na podprocesy.
Apurva Kunkulol
12

Nie zamierzam szczegółowo wyjaśniać, co to joinrobi, ale oto etymologia i intuicja, która za tym stoi, co powinno pomóc ci łatwiej zapamiętać jego znaczenie.

Chodzi o to, że wykonanie „ rozwidla ” wiele procesów, z których jeden jest panem, a pozostali pracownicy (lub „niewolnicy”). Kiedy robotnicy skończą, „dołączają” do kapitana, aby można było wznowić seryjną egzekucję.

joinMetoda powoduje, że proces główny czekać na pracownika do niej przyłączyć. Metoda mogłaby być lepiej nazwana „wait”, ponieważ takie właśnie zachowanie powoduje w masterze (i tak nazywa się w POSIX, chociaż wątki POSIX nazywają ją również „join”). Łączenie następuje tylko w wyniku prawidłowej współpracy nici, nie jest to coś, co robi mistrz .

Nazwy „fork” i „join” są używane w tym znaczeniu w przetwarzaniu wieloprocesowym od 1963 roku .

larsmana
źródło
Więc w pewnym sensie to użycie tego słowa joinmogło poprzedzać jego użycie w odniesieniu do konkatenacji, w przeciwieństwie do odwrotnej sytuacji.
MikeiLL
1
Jest mało prawdopodobne, że użycie w konkatenacji pochodzi z użycia w przetwarzaniu wieloprocesowym; raczej oba zmysły wywodzą się oddzielnie z prostego angielskiego znaczenia tego słowa.
Russell Borogove
2

join()służy do oczekiwania na zakończenie procesu roboczego. Należy zadzwonić close()lub terminate()przed użyciem join().

Podobnie jak @Russell wspomniany łączenie jest przeciwieństwem forka (który wywołuje podprocesy).

Aby dołączyć do uruchomienia, musisz uruchomić, close()co zapobiegnie przesyłaniu kolejnych zadań do puli i wyjdzie po zakończeniu wszystkich zadań. Alternatywnie bieganieterminate() zakończy się natychmiastowym zatrzymaniem wszystkich procesów roboczych.

"the child process will sit idle and not terminate, becoming a zombie you must manually kill" jest to możliwe, gdy proces główny (nadrzędny) kończy pracę, ale proces potomny nadal działa, a po zakończeniu nie ma procesu nadrzędnego, do którego mógłby zwrócić swój kod zakończenia.

Ani Menon
źródło
2

Te join(), zapewnia połączenia że kolejne linie kodu nie są wywoływane przed wieloprocesorowe wszystkie procesy są zakończone.

Na przykład bez tego join()poniższy kod zostanie wywołany restart_program()jeszcze przed zakończeniem procesów, co jest podobne do asynchronicznego i nie jest tym, czego chcemy (możesz spróbować):

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
źródło
0

Aby poczekać, aż proces zakończy swoją pracę i zakończy działanie, użyj metody join ().

i

Uwaga Ważne jest, aby dołączyć () do procesu po jego zakończeniu, aby dać maszynom działającym w tle czas na zaktualizowanie stanu obiektu w celu odzwierciedlenia zakończenia.

To dobry przykład pomógł mi to zrozumieć: tutaj

Osobiście zauważyłem, że mój główny proces został wstrzymany, dopóki dziecko nie zakończyło swojego procesu przy użyciu metody join (), która pokonała cel, w jakim używałem multiprocessing.Process()w pierwszej kolejności.

Josh
źródło