Do czego służy join () w wątkach Pythona?

197

Studiowałem wątki w pythonie i natknąłem się join().

Autor powiedział, że jeśli wątek jest w trybie demona, muszę go użyć join(), aby wątek mógł zakończyć się przed zakończeniem głównego wątku.

ale widziałem go również używającego, t.join()chociaż ttak nie byłodaemon

przykładowy kod to

import threading
import time
import logging

logging.basicConfig(level=logging.DEBUG,
                    format='(%(threadName)-10s) %(message)s',
                    )

def daemon():
    logging.debug('Starting')
    time.sleep(2)
    logging.debug('Exiting')

d = threading.Thread(name='daemon', target=daemon)
d.setDaemon(True)

def non_daemon():
    logging.debug('Starting')
    logging.debug('Exiting')

t = threading.Thread(name='non-daemon', target=non_daemon)

d.start()
t.start()

d.join()
t.join()

nie wiem, co to jest, t.join()ponieważ nie jest demonem, i nie widzę żadnych zmian, nawet jeśli go usunę

użytkownik192362127
źródło
13
+1 za tytuł. Wydaje się, że „Join” jest specjalnie zaprojektowany, aby zachęcać do słabej wydajności (poprzez ciągłe tworzenie / kończenie / niszczenie wątków), blokowanie GUI, (oczekiwanie w modułach obsługi zdarzeń) i awarie zamykania aplikacji (oczekiwanie na zakończenie nieprzerwanych wątków). Uwaga - nie tylko Python, jest to anty-wzorzec międzyjęzykowy.
Martin James

Odpowiedzi:

287

Trochę niezdarna ascii-art, aby zademonstrować mechanizm: join()Przypuszczalnie nazywany jest przez główny wątek. Może być również wywołany przez inny wątek, ale niepotrzebnie skomplikowałby schemat.

join-calling powinien być umieszczony na ścieżce głównego wątku, ale aby wyrazić relację wątku i uprościć go tak prosto, jak to możliwe, wybieram umieszczenie go w wątku potomnym.

without join:
+---+---+------------------                     main-thread
    |   |
    |   +...........                            child-thread(short)
    +..................................         child-thread(long)

with join
+---+---+------------------***********+###      main-thread
    |   |                             |
    |   +...........join()            |         child-thread(short)
    +......................join()......         child-thread(long)

with join and daemon thread
+-+--+---+------------------***********+###     parent-thread
  |  |   |                             |
  |  |   +...........join()            |        child-thread(short)
  |  +......................join()......        child-thread(long)
  +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,     child-thread(long + daemonized)

'-' main-thread/parent-thread/main-program execution
'.' child-thread execution
'#' optional parent-thread execution after join()-blocked parent-thread could 
    continue
'*' main-thread 'sleeping' in join-method, waiting for child-thread to finish
',' daemonized thread - 'ignores' lifetime of other threads;
    terminates when main-programs exits; is normally meant for 
    join-independent tasks

Dlatego powodem, dla którego nie widzisz żadnych zmian, jest to, że Twój główny wątek nic nie robi po twoim join. Można powiedzieć, że joinjest (tylko) istotny dla przepływu wykonania głównego wątku.

Jeśli na przykład chcesz jednocześnie pobrać kilka stron, aby połączyć je w jedną dużą stronę, możesz rozpocząć jednoczesne pobieranie za pomocą wątków, ale musisz poczekać, aż ostatnia strona / wątek zostanie zakończony, zanim zaczniesz składać pojedynczą stronę z wielu. Właśnie wtedy używasz join().

Don Pytanie
źródło
Proszę potwierdzić, że demonizowany wątek może zostać przyłączony () bez blokowania wykonywania programu?
Aviator45003
@ Aviator45003: Tak, za pomocą argumentu Timeout jak: demon_thread.join(0.0), join()to poprzez blokowanie bez względu na atrybut daemonized domyślnie. Ale dołączenie do demonizowanego wątku otwiera najprawdopodobniej całą kłopot! Zastanawiam się teraz nad usunięciem join()wywołania z mojego małego diagramu dla wątku demona ...
Don Pytanie
@DonQuestion Więc jeśli włączymy, czy daemon=Truenie potrzebujemy, join()jeśli potrzebujemy join()na końcu kodu?
Benyamin Jafari
@BenyaminJafari: Tak. Jeśli nie, to główny wątek (= program) wyjdzie, jeśli pozostanie tylko wątek demona. Ale natura wątku (python) demona polega na tym, że główny wątek nie dba o to, czy to zadanie w tle jest nadal uruchomione. Zastanowię się, jak rozwinąć tę kwestię w mojej odpowiedzi, aby wyjaśnić ten problem. Dzięki za komentarz!
Don Pytanie
W pierwszym przypadku, kiedy program się main threadzakończy, czy program zakończy się, nie pozwalając, by child-thread(long)sam się uruchomił (tzn. child-thread(long)Nie jest całkowicie ukończony)?
skytree
65

Prosto z dokumentów

join ([limit czasu]) Poczekaj, aż wątek się zakończy. To blokuje wątek wywołujący, dopóki wątek, którego wywoływana jest metoda join (), nie zakończy się - normalnie lub przez nieobsługiwany wyjątek - lub do momentu wystąpienia opcjonalnego limitu czasu.

Oznacza to, że główny wątek, który się odradza ti dczeka na tzakończenie, dopóki się nie skończy.

W zależności od logiki używanej przez program, możesz poczekać, aż wątek się zakończy, zanim główny wątek będzie kontynuowany.

Również z dokumentów:

Wątek można oznaczyć jako „wątek demona”. Znaczenie tej flagi polega na tym, że cały program Python kończy działanie, gdy pozostaną tylko wątki demona.

Prosty przykład, powiedzmy, że mamy to:

def non_daemon():
    time.sleep(5)
    print 'Test non-daemon'

t = threading.Thread(name='non-daemon', target=non_daemon)

t.start()

Który kończy się na:

print 'Test one'
t.join()
print 'Test two'

Spowoduje to wygenerowanie:

Test one
Test non-daemon
Test two

Tutaj wątek główny jawnie czeka na zakończenie twątku, aż zadzwoni printpo raz drugi.

Alternatywnie, jeśli mielibyśmy to:

print 'Test one'
print 'Test two'
t.join()

Otrzymamy ten wynik:

Test one
Test two
Test non-daemon

Tutaj wykonujemy naszą pracę w głównym wątku, a następnie czekamy na zakończenie twątku. W takim przypadku możemy nawet usunąć jawne połączenie, t.join()a program domyślnie poczeka na tzakończenie.

dmg
źródło
Czy możesz zrobić trochę zmian w moim kodzie, aby zobaczyć różnicę t.join(). przez dodanie snu lub czegoś innego. w tej chwili mogę zobaczyć dowolny program w programie, nawet jeśli go używam lub nie. ale dla damemona widzę jego wyjście, jeśli korzystam z tego, d.join()którego nie widzę, gdy nie używam d.join ()
user192362127
34

Dzięki za ten wątek - bardzo mi też pomógł.

Nauczyłem się dziś czegoś o .join ().

Te wątki działają równolegle:

d.start()
t.start()
d.join()
t.join()

a te działają sekwencyjnie (nie to, co chciałem):

d.start()
d.join()
t.start()
t.join()

W szczególności starałem się sprytnie i uporządkować:

class Kiki(threading.Thread):
    def __init__(self, time):
        super(Kiki, self).__init__()
        self.time = time
        self.start()
        self.join()

To działa! Ale działa sekwencyjnie. Mogę umieścić self.start () w __ init __, ale nie self.join (). Należy to zrobić po rozpoczęciu każdego wątku.

join () powoduje, że główny wątek czeka na zakończenie wątku. W przeciwnym razie wątek działa sam.

Tak więc jednym ze sposobów myślenia o złączeniu () jako „wstrzymaniu” głównego wątku - jest to rodzaj usuwania wątków z wątku i wykonywania sekwencyjnego w głównym wątku, zanim główny wątek będzie mógł kontynuować. Zapewnia, że ​​Twój wątek jest kompletny, zanim główny wątek przejdzie do przodu. Zauważ, że oznacza to, że nic nie szkodzi, jeśli Twój wątek jest już zakończony przed wywołaniem funkcji join () - główny wątek jest po prostu zwalniany natychmiast po wywołaniu funkcji join ().

W rzeczywistości właśnie teraz przychodzi mi do głowy, że główny wątek czeka w d.join (), aż wątek d zakończy się, zanim przejdzie do t.join ().

W rzeczywistości, aby być bardzo jasnym, rozważ ten kod:

import threading
import time

class Kiki(threading.Thread):
    def __init__(self, time):
        super(Kiki, self).__init__()
        self.time = time
        self.start()

    def run(self):
        print self.time, " seconds start!"
        for i in range(0,self.time):
            time.sleep(1)
            print "1 sec of ", self.time
        print self.time, " seconds finished!"


t1 = Kiki(3)
t2 = Kiki(2)
t3 = Kiki(1)
t1.join()
print "t1.join() finished"
t2.join()
print "t2.join() finished"
t3.join()
print "t3.join() finished"

Tworzy to wyjście (zwróć uwagę, w jaki sposób instrukcje drukowania są ze sobą powiązane.)

$ python test_thread.py
32   seconds start! seconds start!1

 seconds start!
1 sec of  1
 1 sec of 1  seconds finished!
 21 sec of
3
1 sec of  3
1 sec of  2
2  seconds finished!
1 sec of  3
3  seconds finished!
t1.join() finished
t2.join() finished
t3.join() finished
$ 

T1.join () utrzymuje główny wątek. Wszystkie trzy wątki zakończą się przed zakończeniem t1.join (), a główny wątek przejdzie do wykonania drukowania, następnie t2.join (), następnie wydrukuj następnie t3.join (), a następnie wydrukuj.

Korekty mile widziane. Jestem również nowy w wątkach.

(Uwaga: jeśli jesteś zainteresowany, piszę kod dla DrinkBota i potrzebuję wątków, aby uruchomić pompy składników jednocześnie, a nie sekwencyjnie - mniej czasu na czekanie na każdego drinka.)

Kiki Jewell
źródło
Hej, jestem również nowy w wątkach w Pythonie i mylę się co do głównego wątku. Czy pierwszy wątek jest głównym wątkiem? Jeśli nie, proszę o pomoc?
Rohit Khatri,
Głównym wątkiem jest sam program. Stąd każdy wątek jest rozwidlony. Są one następnie ponownie dołączane - ponieważ w komendzie join () program czeka na zakończenie wątku, zanim będzie kontynuował działanie.
Kiki Jewell
15

Metoda join ()

blokuje wątek wywołujący do momentu zakończenia wątku, którego wywołana jest metoda join ().

Źródło: http://docs.python.org/2/library/threading.html

Ketouem
źródło
14
więc po co łączyć? patrz pytanie OP, nie parafrazuj tylko dokumentów
Don Pytanie
@DonQuestion próbowałem nawet dodać sleep.timer (20) w wątku innym niż demon bez użycia, t.join()a program nadal czeka na to przed zakończeniem. nie widzę tu żadnego zastosowania t.join()w moim kodzie
user192362127
zobacz moją odpowiedź w celu uzyskania dalszych wyjaśnień. w odniesieniu do twojego sleep.timer w non-demon -> wątek demona jest oddzielony od czasu życia swojego wątku macierzystego, a zatem czas życia demonizowanego wątku nie będzie miał wpływu na wątki rodzica / rodzeństwa i odwrotnie .
Don Pytanie
2
Zastanawiająca jest terminologia „dołącz” i „blok”. „Zablokowane” sugeruje, że proces wywoływania jest „zablokowany” przed wykonaniem dowolnej liczby czynności, które musi jeszcze wykonać, podczas gdy w rzeczywistości jest po prostu zablokowany przed zakończeniem (powrotem do systemu operacyjnego), a nie więcej. Z tego samego powodu nie jest tak oczywiste, że istnieje główny wątek wywołujący wątek potomny w celu „dołączenia” (tj. Zakończenia). Don Q, dzięki za wyjaśnienie.
RolfBly
4

Proste zrozumienie,

z dołączeniem - tłumacz będzie czekać na zakończenie lub zakończenie procesu

>>> from threading import Thread
>>> import time
>>> def sam():
...   print 'started'
...   time.sleep(10)
...   print 'waiting for 10sec'
... 
>>> t = Thread(target=sam)
>>> t.start()
started

>>> t.join() # with join interpreter will wait until your process get completed or terminated
done?   # this line printed after thread execution stopped i.e after 10sec
waiting for 10sec
>>> done?

bez dołączenia - tłumacz nie będzie czekał na zakończenie procesu ,

>>> t = Thread(target=sam)
>>> t.start()
started
>>> print 'yes done' #without join interpreter wont wait until process get terminated
yes done
>>> waiting for 10sec
Mohideen bin Mohammed
źródło
1

Podczas wykonywania join(t)funkcji zarówno dla wątku nie-demonicznego, jak i wątku demonicznego, wątek główny (lub proces główny) powinien poczekać tkilka sekund, a następnie może przejść dalej do pracy nad własnym procesem. W ciągu tsekundy oczekiwania oba wątki podrzędne powinny zrobić to, co mogą, na przykład wydrukować tekst. Po tkilku sekundach, jeśli wątek inny niż demon nadal nie ukończył zadania, i nadal może go ukończyć po zakończeniu procesu głównego, ale w przypadku wątku demona po prostu przegapił swoje okno możliwości. Jednak ostatecznie umrze po wyjściu z programu python. Popraw mnie, jeśli coś jest nie tak.

użytkownik1342336
źródło
1

W Pythonie 3.x join () służy do łączenia wątku z głównym wątkiem, tzn. Gdy joach () jest używany do określonego wątku, główny wątek przestanie działać do momentu zakończenia wykonywania połączonego wątku.

#1 - Without Join():
import threading
import time
def loiter():
    print('You are loitering!')
    time.sleep(5)
    print('You are not loitering anymore!')

t1 = threading.Thread(target = loiter)
t1.start()
print('Hey, I do not want to loiter!')
'''
Output without join()--> 
You are loitering!
Hey, I do not want to loiter!
You are not loitering anymore! #After 5 seconds --> This statement will be printed

'''
#2 - With Join():
import threading
import time
def loiter():
    print('You are loitering!')
    time.sleep(5)
    print('You are not loitering anymore!')

t1 = threading.Thread(target = loiter)
t1.start()
t1.join()
print('Hey, I do not want to loiter!')

'''
Output with join() -->
You are loitering!
You are not loitering anymore! #After 5 seconds --> This statement will be printed
Hey, I do not want to loiter! 

'''
Shishir Nanoty
źródło
0

W tym przykładzie pokazano .join()działanie:

import threading
import time

def threaded_worker():
    for r in range(10):
        print('Other: ', r)
        time.sleep(2)

thread_ = threading.Timer(1, threaded_worker)
thread_.daemon = True  # If the main thread kills, this thread will be killed too. 
thread_.start()

flag = True

for i in range(10):
    print('Main: ', i)
    time.sleep(2)
    if flag and i > 4:
        print(
            '''
            Threaded_worker() joined to the main thread. 
            Now we have a sequential behavior instead of concurrency.
            ''')
        thread_.join()
        flag = False

Na zewnątrz:

Main:  0
Other:  0
Main:  1
Other:  1
Main:  2
Other:  2
Main:  3
Other:  3
Main:  4
Other:  4
Main:  5
Other:  5

            Threaded_worker() joined to the main thread. 
            Now we have a sequential behavior instead of concurrency.

Other:  6
Other:  7
Other:  8
Other:  9
Main:  6
Main:  7
Main:  8
Main:  9
Benyamin Jafari
źródło
0

Istnieje kilka powodów, dla których główny wątek (lub jakikolwiek inny wątek) dołącza do innych wątków

  1. Wątek mógł utworzyć lub zatrzymać (zablokować) niektóre zasoby. Wątek łączący może być w stanie wyczyścić zasoby w jego imieniu

  2. join () to naturalne blokujące wywołanie dla wątku wywołującego łączenie, które będzie kontynuowane po zakończeniu wywoływanego wątku.

Jeśli program w języku Python nie dołącza do innych wątków, interpreter języka Python nadal dołącza do niego wątki niebędące demonami.

yoonghm
źródło
-2

„Po co korzystać z join ()?” mówisz. Naprawdę jest to ta sama odpowiedź, co „po co zamykanie plików, skoro Python i system operacyjny zamkną mój plik dla mnie, gdy mój program zakończy pracę?”.

To po prostu kwestia dobrego programowania. Powinieneś dołączyć () do swoich wątków w punkcie kodu, w którym wątek nie powinien być uruchomiony, albo dlatego, że musisz upewnić się, że wątek nie działa, aby zakłócać twój własny kod, lub że chcesz zachowywać się poprawnie w większy system.

Możesz powiedzieć „Nie chcę, aby mój kod opóźniał udzielenie odpowiedzi” tylko ze względu na dodatkowy czas, którego może wymagać funkcja join (). Może to być całkowicie poprawne w niektórych scenariuszach, ale musisz teraz wziąć pod uwagę, że twój kod „pozostawia cruft w pobliżu dla Pythona i systemu operacyjnego do wyczyszczenia”. Jeśli robisz to ze względu na wydajność, gorąco zachęcam do udokumentowania tego zachowania. Jest to szczególnie prawdziwe, jeśli budujesz bibliotekę / pakiet, z którego inni powinni korzystać.

Nie ma powodu, aby nie dołączać (), oprócz powodów wydajności, i argumentowałbym, że twój kod nie musi działać tak dobrze.

Chris Cogdon
źródło
6
To, co mówisz o usuwaniu wątków, nie jest prawdą. Spójrz na kod źródłowy threading.Thread.join (). Wystarczy, że zaczekasz na zamek, a następnie wrócisz. Nic nie jest faktycznie sprzątane.
Collin
1
@Collin - sam wątek może przechowywać zasoby, w takim przypadku interpreter i system operacyjny rzeczywiście będą musiały wyczyścić „cruft”.
qneill,
1
Ponownie spójrz na kod źródłowy threading.Thread.join (). Nie ma tam nic, co uruchamiałoby zbieranie zasobów.
Collin,
To niekoniecznie (i jak mówisz, wcale) moduł wątków, który przechowuje zasoby, ale sam wątek. Użycie join () oznacza, że ​​czekasz, aż wątek skończy robić to, co chciał, co może obejmować przydzielanie i zwalnianie zasobów.
Chris Cogdon,
2
To, czy czekasz, czy nie, nie ma wpływu na zwolnienie zasobów przechowywanych przez wątek. Nie jestem pewien, dlaczego łączysz to z telefonowaniem join().
Collin,