Was ist die Verwendung von join () im Python-Threading?

197

Ich habe das Python-Threading studiert und bin darauf gestoßen join().

Der Autor sagte, wenn sich der Thread im Daemon-Modus befindet, muss ich ihn verwenden, join()damit der Thread sich selbst beenden kann, bevor der Haupt-Thread beendet wird.

aber ich habe ihn auch benutzen sehen t.join(), obwohl tes nicht wardaemon

Beispielcode ist dies

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

Ich weiß nicht, was verwendet wird, t.join()da es kein Daemon ist und ich keine Änderung sehen kann, selbst wenn ich es entferne

user192362127
quelle
13
+1 für den Titel. 'Join' scheint speziell entwickelt worden zu sein, um eine schlechte Leistung (durch kontinuierliches Erstellen / Beenden / Zerstören von Threads), GUI-Sperren (Warten in Event-Handlern) und Fehler beim Herunterfahren der App (Warten auf das Beenden unterbrechungsfreier Threads) zu fördern. Hinweis - nicht nur Python, sondern ein sprachübergreifendes Anti-Pattern.
Martin James

Antworten:

286

Eine etwas ungeschickte ASCII-Kunst, um den Mechanismus zu demonstrieren: Die join()wird vermutlich vom Hauptfaden genannt. Es könnte auch von einem anderen Thread aufgerufen werden, würde das Diagramm jedoch unnötig komplizieren.

join-calling sollte in der Spur des Haupt-Threads platziert werden, aber um die Thread-Beziehung auszudrücken und sie so einfach wie möglich zu halten, habe ich mich dafür entschieden, sie stattdessen im untergeordneten Thread zu platzieren.

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

Der Grund, warum Sie keine Änderungen sehen, ist, dass Ihr Haupt-Thread nichts nach Ihrem tut join. Man könnte sagen, es joinist (nur) relevant für den Ausführungsfluss des Hauptthreads.

Wenn Sie beispielsweise mehrere Seiten gleichzeitig herunterladen möchten, um sie zu einer einzigen großen Seite zu verketten, können Sie gleichzeitige Downloads mithilfe von Threads starten, müssen jedoch warten, bis die letzte Seite / der letzte Thread fertig ist, bevor Sie mit dem Zusammenstellen einer einzelnen Seite beginnen von vielen. Das ist, wenn Sie verwenden join().

Don Frage
quelle
Bitte bestätigen Sie, dass ein daemonisierter Thread verbunden werden kann (), ohne die Programmausführung zu blockieren.
Aviator45003
@ Aviator45003: Ja, durch wie das Timeout - Argument: demon_thread.join(0.0), join()ist standardmäßig ohne Rücksicht auf das daemonisierte Attribut blockiert. Aber das Beitreten zu einem dämonisierten Thread eröffnet höchstwahrscheinlich eine ganze Dose Ärger! Ich denke jetzt darüber nach, den join()Aufruf in meinem kleinen Diagramm für den Daemon-Thread zu entfernen ...
Don Question
@DonQuestion Wenn wir also auf daemon=Truesetzen, brauchen wir nicht das, join()wenn wir das join()am Ende des Codes brauchen ?
Benyamin Jafari
@ BenyaminJafari: Ja. Wenn nicht, würde der Haupt-Thread (= Programm) beendet, wenn nur der Daemon-Thread übrig bleibt. Die Natur eines (Python-) Daemon-Threads ist jedoch, dass es dem Haupt-Thread egal ist, ob diese Hintergrundaufgabe noch ausgeführt wird. Ich werde in meiner Antwort darüber nachdenken, wie ich darauf näher eingehen kann, um dieses Problem zu klären. Vielen Dank für Ihren Kommentar!
Don Frage
main threadWird das Programm im ersten Fall nach Beendigung beendet, ohne dass das child-thread(long)Ende selbst ausgeführt wird (dh child-thread(long)nicht vollständig abgeschlossen ist)?
Skytree
65

Direkt aus den Dokumenten

join ([timeout]) Warten Sie, bis der Thread beendet ist. Dadurch wird der aufrufende Thread blockiert, bis der Thread, dessen join () -Methode aufgerufen wird, entweder normal oder durch eine nicht behandelte Ausnahme beendet wird oder bis das optionale Zeitlimit auftritt.

Dies bedeutet, dass der Haupt-Thread, der erscheint tund dwartet, tbis er fertig ist, beendet wird.

Abhängig von der Logik, die Ihr Programm verwendet, möchten Sie möglicherweise warten, bis ein Thread beendet ist, bevor Ihr Hauptthread fortgesetzt wird.

Auch aus den Dokumenten:

Ein Thread kann als "Daemon-Thread" gekennzeichnet werden. Die Bedeutung dieses Flags besteht darin, dass das gesamte Python-Programm beendet wird, wenn nur noch Daemon-Threads übrig sind.

Ein einfaches Beispiel, sagen wir, wir haben folgendes:

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

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

t.start()

Was endet mit:

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

Dies wird Folgendes ausgeben:

Test one
Test non-daemon
Test two

Hier wartet der Master-Thread explizit auf das tEnde des Threads, bis er printdas zweite Mal aufgerufen wird.

Alternativ, wenn wir dies hatten:

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

Wir werden diese Ausgabe erhalten:

Test one
Test two
Test non-daemon

Hier machen wir unsere Arbeit im Haupt-Thread und warten dann, bis der tThread fertig ist. In diesem Fall entfernen wir möglicherweise sogar die explizite Verknüpfung, t.join()und das Programm wartet implizit auf tden Abschluss.

dmg
quelle
Können Sie einige chnage zu meinen Code machen , so dass ich den Unterschied von sehen t.join(). durch Hinzufügen von Schlaf oder etwas anderem. Im Moment kann ich jede Änderung im Programm sehen, selbst wenn ich es benutze oder nicht. aber für damemon kann ich seinen Ausgang sehen, wenn ich benutze, d.join()was ich nicht sehe, wenn ich d.join () nicht benutze
user192362127
34

Danke für diesen Thread - er hat mir auch sehr geholfen.

Ich habe heute etwas über .join () gelernt.

Diese Threads laufen parallel:

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

und diese laufen nacheinander (nicht das, was ich wollte):

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

Insbesondere habe ich versucht, klug und ordentlich zu sein:

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

Das funktioniert! Aber es läuft nacheinander. Ich kann self.start () in __ init __ einfügen, aber nicht self.join (). Dies muss erfolgen, nachdem jeder Thread gestartet wurde.

join () bewirkt, dass der Hauptthread auf das Ende Ihres Threads wartet. Andernfalls wird Ihr Thread von selbst ausgeführt.

Eine Möglichkeit, sich join () als "Halten" des Hauptthreads vorzustellen, besteht darin, den Thread zu entfädeln und nacheinander im Hauptthread auszuführen, bevor der Hauptthread fortgesetzt werden kann. Es stellt sicher, dass Ihr Thread vollständig ist, bevor sich der Haupt-Thread vorwärts bewegt. Beachten Sie, dass dies in Ordnung ist, wenn Ihr Thread bereits fertig ist, bevor Sie join () aufrufen. Der Hauptthread wird einfach sofort freigegeben, wenn join () aufgerufen wird.

Tatsächlich fällt mir gerade ein, dass der Haupt-Thread bei d.join () wartet, bis Thread d beendet ist, bevor er zu t.join () übergeht.

Um ganz klar zu sein, betrachten Sie diesen Code:

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"

Diese Ausgabe wird erzeugt (beachten Sie, wie die Druckanweisungen ineinander eingefädelt sind.)

$ 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
$ 

Das t1.join () hält den Haupt-Thread hoch. Alle drei Threads werden abgeschlossen, bevor t1.join () beendet wird und der Haupt-Thread fortfährt, um den Druck auszuführen. Dann t2.join (), dann print, dann t3.join () und dann print.

Korrekturen sind willkommen. Ich bin auch neu im Threading.

(Hinweis: Falls Sie interessiert sind, schreibe ich Code für einen DrinkBot und benötige Threading, um die Zutatenpumpen gleichzeitig und nicht nacheinander zu betreiben - weniger Zeit, um auf jedes Getränk zu warten.)

Kiki Jewell
quelle
Hey, ich bin auch neu im Python-Threading und verwirrt über den Haupt-Thread. Ist der erste Thread der Haupt-Thread? Wenn nicht, führe mich bitte?
Rohit Khatri
Der Hauptthread ist das Programm selbst. Jeder der Fäden wird von dort gegabelt. Sie werden dann wieder zusammengefügt, da das Programm beim Befehl join () wartet, bis der Thread fertig ist, bevor es weiter ausgeführt wird.
Kiki Jewell
15

Die Methode join ()

blockiert den aufrufenden Thread, bis der Thread beendet wird, dessen join () -Methode aufgerufen wird.

Quelle: http://docs.python.org/2/library/threading.html

Ketouem
quelle
14
Was nützt Join? siehe OP Frage, nicht nur die Dokumente umschreiben
Don Frage
@DonQuestion Ich habe sogar versucht, sleep.timer (20) in einem Nicht-Daemon-Thread hinzuzufügen, ohne es zu verwenden, t.join()und das Programm wartet immer noch darauf, bevor es beendet wird. Ich sehe keine Verwendung von t.join()hier in meinem Code
user192362127
Siehe meine Antwort für weitere Erklärungen. in Bezug auf Ihren sleep.timer in Nicht-Dämonen -> Ein Dämonen-Thread ist von der Lebensdauer seines übergeordneten Threads entkoppelt, sodass die übergeordneten / Geschwister-Threads nicht von der Lebensdauer des dämonisierten Threads beeinflusst werden und umgekehrt .
Don Frage
2
Die Terminologie 'Join' und 'Block' ist rätselhaft. "Blockiert" bedeutet, dass der aufrufende Prozess für eine beliebige Anzahl von Aufgaben "blockiert" ist, während er nur für die Beendigung (Rückkehr zum Betriebssystem) blockiert ist, nicht mehr. Aus dem gleichen Grund ist es nicht so offensichtlich, dass es einen Hauptthread gibt, der einen untergeordneten Thread aufruft, um ihm beizutreten (dh zu beenden). Also, Don Q, danke für die Erklärung.
RolfBly
4

Einfach verstehen,

mit join - Der Interpreter wartet, bis Ihr Prozess abgeschlossen oder beendet ist

>>> 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?

ohne Join - Interpreter wird nicht warten, bis der Prozess beendet wird ,

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

Wenn die join(t)Funktion sowohl für Nicht-Daemon-Threads als auch für Daemon-Threads ausgeführt wird, sollte der Haupt-Thread (oder Hauptprozess) tSekunden warten und kann dann weiter an seinem eigenen Prozess arbeiten. Während der tWartezeit von Sekunden sollten beide untergeordneten Threads das tun, was sie können, z. B. Text ausdrucken. tWenn nach den Sekunden ein Nicht-Daemon-Thread seinen Job immer noch nicht beendet hat und er ihn auch beenden kann, nachdem der Hauptprozess seinen Job beendet hat, hat er für den Daemon-Thread nur das Opportunity-Fenster verpasst. Es wird jedoch irgendwann nach dem Beenden des Python-Programms sterben. Bitte korrigieren Sie mich, wenn etwas nicht stimmt.

user1342336
quelle
1

In Python 3.x wird join () verwendet, um einen Thread mit dem Hauptthread zu verbinden. Wenn join () für einen bestimmten Thread verwendet wird, wird die Ausführung des Hauptthreads beendet, bis die Ausführung des verknüpften Threads abgeschlossen ist.

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

Dieses Beispiel zeigt die .join()Aktion:

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

Aus:

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

Es gibt einige Gründe, warum der Haupt-Thread (oder ein anderer Thread) andere Threads verbindet

  1. Ein Thread hat möglicherweise einige Ressourcen erstellt oder gehalten (gesperrt). Der Join-Calling-Thread kann möglicherweise die Ressourcen in seinem Namen löschen

  2. join () ist ein natürlicher Blockierungsaufruf für den Join-aufrufenden Thread, der fortgesetzt wird, nachdem der aufgerufene Thread beendet wurde.

Wenn ein Python-Programm keine anderen Threads verbindet, verbindet der Python-Interpreter weiterhin Nicht-Daemon-Threads in seinem Namen.

Yoonghm
quelle
-2

"Was nützt die Verwendung von join ()?" du sagst. Wirklich, es ist die gleiche Antwort wie "Was nützt das Schließen von Dateien, da Python und das Betriebssystem meine Datei für mich schließen, wenn mein Programm beendet wird?".

Es ist einfach eine Frage guter Programmierung. Sie sollten Ihre Threads an der Stelle im Code verbinden (), an der der Thread nicht mehr ausgeführt werden soll, entweder weil Sie sicher sein müssen, dass der Thread nicht ausgeführt wird, um Ihren eigenen Code zu stören, oder weil Sie sich in a korrekt verhalten möchten größeres System.

Sie könnten sagen "Ich möchte nicht, dass mein Code die Beantwortung verzögert", nur wegen der zusätzlichen Zeit, die join () möglicherweise benötigt. Dies mag in einigen Szenarien durchaus gültig sein, aber Sie müssen jetzt berücksichtigen, dass Ihr Code "Cruft für Python und das Betriebssystem zur Bereinigung übrig lässt". Wenn Sie dies aus Leistungsgründen tun, empfehle ich Ihnen dringend, dieses Verhalten zu dokumentieren. Dies gilt insbesondere dann, wenn Sie eine Bibliothek / ein Paket erstellen, die / das von anderen erwartet wird.

Es gibt keinen Grund, nicht join (), andere als Performance - Gründe, und ich würde behaupten , dass Ihr Code muss nicht ausführen , dass gut.

Chris Cogdon
quelle
6
Was Sie über das Aufräumen von Threads sagen, ist nicht wahr. Schauen Sie sich den Quellcode von threading.Thread.join () an. Diese Funktion wartet lediglich auf ein Schloss und kehrt dann zurück. Nichts wird tatsächlich aufgeräumt.
Collin
1
@Collin - Der Thread selbst enthält möglicherweise Ressourcen. In diesem Fall müssen Interpreter und Betriebssystem tatsächlich "cruft" bereinigen.
Qneill
1
Schauen Sie sich noch einmal den Quellcode von threading.Thread.join () an. Es gibt nichts, was das Sammeln von Ressourcen auslöst.
Collin
Es ist nicht unbedingt (und wie Sie sagen, überhaupt nicht) das Threading-Modul, das Ressourcen enthält, sondern der Thread selbst. Die Verwendung von join () bedeutet, dass Sie darauf warten, dass der Thread das tut, was er wollte. Dies kann das Zuweisen und Freigeben von Ressourcen umfassen.
Chris Cogdon
2
Ob Sie warten oder nicht, hat keinen Einfluss darauf, wann die vom Thread gehaltenen Ressourcen freigegeben werden. Ich bin mir nicht sicher, warum Sie dies mit einem Anruf verbinden join().
Collin