À quoi sert join () dans le filetage Python?

197

J'étudiais le filetage python et suis tombé sur join().

L'auteur a dit que si le thread est en mode démon, je dois l'utiliser join()pour que le thread puisse se terminer avant la fin du thread principal.

mais je l'ai également vu utiliser t.join()même si ce tn'était pasdaemon

exemple de code est-ce

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

Je ne sais pas à quoi t.join()ça sert car ce n'est pas un démon et je ne vois aucun changement même si je le supprime

user192362127
la source
13
+1 pour le titre. `` Join '' semble être spécialement conçu pour encourager les mauvaises performances (en créant / terminant / détruisant continuellement des threads), les blocages de l'interface graphique (en attente dans les gestionnaires d'événements) et les échecs de fermeture des applications (en attendant la fin des threads sans interruption). Remarque - pas seulement Python, il s'agit d'un anti-modèle multilingue.
Martin James

Réponses:

288

Un ascii-art quelque peu maladroit pour démontrer le mécanisme: le join()est vraisemblablement appelé par le fil principal. Il pourrait également être appelé par un autre thread, mais compliquerait inutilement le diagramme.

join-l'appel devrait être placé dans la piste du thread principal, mais pour exprimer la relation de thread et la garder aussi simple que possible, je choisis de le placer dans le thread enfant à la place.

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

Donc, la raison pour laquelle vous ne voyez aucun changement est que votre thread principal ne fait rien après votre join. On pourrait dire que joinc'est (seulement) pertinent pour le flux d'exécution du thread principal.

Si, par exemple, vous souhaitez télécharger simultanément un tas de pages pour les concaténer en une seule grande page, vous pouvez démarrer des téléchargements simultanés à l'aide de threads, mais vous devez attendre la fin de la dernière page / thread avant de commencer à assembler une seule page. sur beaucoup. C'est alors que vous utilisez join().

Don Question
la source
Veuillez confirmer qu'un thread démonisé peut être joint () sans bloquer l'exécution du programme?
Aviator45003
@ Aviator45003: Oui, en utilisant l'argument timeout comme: demon_thread.join(0.0), join()est par défaut de blocage sans tenir compte de l'attribut daemon. Mais rejoindre un fil diabolisé ouvre très probablement un ensemble de problèmes! J'envisage maintenant de supprimer l' join()appel dans mon petit diagramme pour le thread démon ...
Don Question
@DonQuestion Donc, si nous activons, n'avons daemon=True-nous pas besoin de join()si nous avons besoin de la join()fin du code?
Benyamin Jafari
@BenyaminJafari: Oui. Si ce n'est pas le cas, le thread principal (= programme) se fermera, s'il ne reste que le thread du démon. Mais la nature d'un thread démon (python) est que le thread principal ne se soucie pas si cette tâche en arrière-plan est toujours en cours d'exécution. Je vais réfléchir à la façon de développer cela dans ma réponse, pour éclaircir ce problème. Merci pour votre commentaire!
Don Question
Dans le premier cas, à la main threadfin, le programme se terminera-t-il sans laisser la child-thread(long)finition s'exécuter lui-même (c'est child-thread(long)-à- dire qu'il n'est pas complètement terminé)?
skytree
65

Directement des documents

join ([timeout]) Attendez la fin du thread. Cela bloque le thread appelant jusqu'à ce que le thread dont la méthode join () est appelée se termine - normalement ou via une exception non gérée - ou jusqu'à ce que le délai d'expiration facultatif se produise.

Cela signifie que le thread principal qui apparaît tet dattend tjusqu'à ce qu'il se termine.

Selon la logique utilisée par votre programme, vous souhaiterez peut-être attendre la fin d'un thread avant de poursuivre votre thread principal.

Également à partir des documents:

Un thread peut être signalé comme un «thread démon». La signification de cet indicateur est que le programme Python entier se ferme lorsqu'il ne reste que des threads démon.

Un exemple simple, disons que nous avons ceci:

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

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

t.start()

Qui se termine par:

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

Cela produira:

Test one
Test non-daemon
Test two

Ici, le thread principal attend explicitement la tfin du thread jusqu'à ce qu'il appelle printla deuxième fois.

Alternativement, si nous avions ceci:

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

Nous obtiendrons cette sortie:

Test one
Test two
Test non-daemon

Ici, nous faisons notre travail dans le thread principal, puis nous attendons que le tthread se termine. Dans ce cas, nous pourrions même supprimer la jointure explicite t.join()et le programme attendra implicitement la tfin.

dmg
la source
Pouvez-vous apporter quelques modifications à mon code afin que je puisse voir la différence de t.join(). en ajoutant du sommeil ou quelque chose d'autre. au moment où je peux voir n'importe quel chnage dans le programme même si je l'utilise ou non. mais pour damemon je peux voir sa sortie si j'utilise d.join()ce que je ne vois pas quand je n'utilise pas d.join ()
user192362127
34

Merci pour ce fil - cela m'a beaucoup aidé aussi.

J'ai appris quelque chose sur .join () aujourd'hui.

Ces threads s'exécutent en parallèle:

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

et ceux-ci fonctionnent séquentiellement (pas ce que je voulais):

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

En particulier, j'essayais d'intelligent et de bien rangé:

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

Cela marche! Mais cela fonctionne séquentiellement. Je peux mettre self.start () dans __ init __, mais pas self.join (). Cela doit être fait après le démarrage de chaque thread.

join () est ce qui fait que le thread principal attend la fin de votre thread. Sinon, votre thread s'exécute tout seul.

Donc, une façon de penser à join () comme un "hold" sur le thread principal - il sorte de dé-thread votre thread et s'exécute séquentiellement dans le thread principal, avant que le thread principal puisse continuer. Il garantit que votre thread est terminé avant que le thread principal n'avance. Notez que cela signifie que tout va bien si votre thread est déjà terminé avant d'appeler join () - le thread principal est simplement libéré immédiatement lorsque join () est appelé.

En fait, il me vient juste à l'esprit que le thread principal attend à d.join () jusqu'à ce que le thread d se termine avant de passer à t.join ().

En fait, pour être très clair, considérez ce 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"

Il produit cette sortie (notez comment les instructions d'impression sont enfilées les unes dans les autres.)

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

Le t1.join () maintient le thread principal. Les trois threads se terminent avant la fin de t1.join () et le thread principal passe à l'exécution de l'impression, puis de t2.join () puis de l'impression, puis de t3.join (), puis de l'impression.

Les corrections sont les bienvenues. Je suis également nouveau dans le filetage.

(Remarque: au cas où vous seriez intéressé, j'écris du code pour un DrinkBot, et j'ai besoin d'un filetage pour exécuter les pompes à ingrédients simultanément plutôt que séquentiellement - moins de temps à attendre pour chaque boisson.)

Kiki Jewell
la source
Hé, je suis également nouveau dans le filetage python et confus au sujet du fil principal, le premier fil est-il le fil principal, sinon, veuillez me guider?
Rohit Khatri
Le fil conducteur est le programme lui-même. Chacun des fils est bifurqué à partir de là. Ils sont ensuite rejoints - car à la commande join (), le programme attend que le thread soit terminé avant de continuer à s'exécuter.
Kiki Jewell
15

La méthode join ()

bloque le thread appelant jusqu'à ce que le thread dont la méthode join () est appelée soit terminé.

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

Ketouem
la source
14
alors à quoi sert join? voir la question OP, ne paraphrasez pas seulement les documents
Don Question
@DonQuestion j'ai même essayé d'ajouter sleep.timer (20) dans un thread non démon sans utiliser t.join()et le programme l'attend toujours avant la fin. je ne vois aucune utilisation d' t.join()ici dans mon code
user192362127
voir ma réponse, pour plus d'explications. concernant votre sleep.timer en non-démon -> un fil démon est découplé de la durée de vie de son fil parent et donc les fils parents / frères ne seront pas affectés par la durée de vie du fil diabolisé et vice versa .
Don Question
2
La terminologie «joindre» et «bloquer» est déroutante. `` Bloqué '' suggère que le processus d'appel est `` bloqué '' de faire un certain nombre de choses qu'il doit encore faire, alors qu'en fait, il est simplement bloqué de se terminer (revenir au système d'exploitation), pas plus. De la même manière, il n'est pas si évident qu'il existe un thread principal appelant un thread enfant pour le «rejoindre» (c'est-à-dire terminer). Alors, Don Q, merci pour l'explication.
RolfBly
5

Simple comprendre,

avec join - l'interprète attendra que votre processus soit terminé ou terminé

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

sans se joindre - interprète attendre jusqu'à ce que l' habitude de se processus terminé ,

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

Lors de la création d'une join(t)fonction pour un thread non démon et un thread démon, le thread principal (ou processus principal) doit attendre tquelques secondes, puis peut aller plus loin pour travailler sur son propre processus. Pendant le ttemps d'attente des secondes, les deux fils enfants doivent faire ce qu'ils peuvent faire, comme imprimer du texte. Après les tsecondes, si le thread non démon n'a toujours pas terminé son travail, et il peut toujours le terminer après que le processus principal a terminé son travail, mais pour le thread démon, il a juste raté sa fenêtre d'opportunité. Cependant, il finira par mourir après la fin du programme python. Veuillez me corriger s'il y a quelque chose qui ne va pas.

user1342336
la source
1

En python 3.x, join () est utilisé pour joindre un thread avec le thread principal, c'est-à-dire que lorsque join () est utilisé pour un thread particulier, le thread principal arrêtera de s'exécuter jusqu'à ce que l'exécution du thread joint soit terminée.

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

Cet exemple illustre l' .join()action:

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

En dehors:

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

Il y a quelques raisons pour que le thread principal (ou tout autre thread) rejoigne d'autres threads

  1. Un thread peut avoir créé ou contenir (verrouiller) certaines ressources. Le thread appelant à joindre peut être en mesure d'effacer les ressources en son nom

  2. join () est un appel de blocage naturel pour que le thread appelant la jointure continue après la fin du thread appelé.

Si un programme python ne joint pas d'autres threads, l'interpréteur python joindra toujours les threads non démons en son nom.

yoonghm
la source
-2

"Quelle est l'utilité d'utiliser join ()?" vous dites. Vraiment, c'est la même réponse que "quelle est l'utilité de fermer des fichiers, car python et le système d'exploitation fermeront mon fichier pour moi lorsque mon programme se fermera?".

C'est simplement une question de bonne programmation. Vous devez joindre () vos threads au point dans le code que le thread ne doit pas être exécuté, soit parce que vous devez vous assurer que le thread ne s'exécute pas pour interférer avec votre propre code, soit que vous souhaitez vous comporter correctement dans un système plus grand.

Vous pourriez dire «Je ne veux pas que mon code retarde la réponse» simplement à cause du temps supplémentaire que la jointure () pourrait nécessiter. Cela peut être parfaitement valable dans certains scénarios, mais vous devez maintenant tenir compte du fait que votre code "laisse la saloperie pour python et le système d'exploitation à nettoyer". Si vous le faites pour des raisons de performances, je vous encourage fortement à documenter ce comportement. Cela est particulièrement vrai si vous créez une bibliothèque / un package que d'autres sont censés utiliser.

Il n'y a aucune raison de ne pas se joindre à (), autres que des raisons de performance, et je dirais que votre code n'a pas besoin d'effectuer que bien.

Chris Cogdon
la source
6
Ce que vous dites sur le nettoyage des threads n'est pas vrai. Jetez un œil au code source de threading.Thread.join (). Tout ce que cette fonction fait, c'est attendre sur un verrou, puis revenir. Rien n'est réellement nettoyé.
Collin
1
@Collin - Le thread lui-même peut contenir des ressources, dans ce cas, l'interpréteur et le système d'exploitation devront en effet nettoyer "cruft".
qneill
1
Encore une fois, regardez le code source de threading.Thread.join (). Il n'y a rien là-dedans qui déclenche la collecte de ressources.
Collin
Ce n'est pas nécessairement (et comme vous le dites, pas du tout) le module de thread qui contient des ressources, mais le thread lui-même. L'utilisation de join () signifie que vous attendez que le thread finisse de faire ce qu'il voulait faire, ce qui pourrait inclure l'allocation et la libération de ressources.
Chris Cogdon
2
Que vous attendiez ou non n'affecte pas le moment où les ressources détenues par le thread sont libérées. Je ne sais pas pourquoi vous liez cela à l'appel join().
Collin