A che serve join () nel threading Python?


197

Stavo studiando il threading Python e mi sono imbattuto join().

L'autore ha detto che se il thread è in modalità demone, allora devo usarlo in join()modo che il thread possa finire da solo prima che il thread principale termini.

ma l'ho anche visto usare t.join()anche se tnon lo eradaemon

il codice di esempio è questo

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

non so a cosa serva t.join()perché non è un demone e non vedo alcun cambiamento anche se lo rimuovo


13
+1 per il titolo. 'Join' sembra essere appositamente progettato per incoraggiare scarse prestazioni, (creando / terminando / distruggendo continuamente thread), blocchi della GUI, (in attesa di gestori di eventi) e errori di arresto delle app, (in attesa che i thread ininterrotti si interrompano). Nota: non solo Python, questo è un anti-pattern multilingue.
Martin James,

Risposte:


288

Un'arte ascii un po 'goffa per dimostrare il meccanismo: join()è presumibilmente chiamato dal thread principale. Potrebbe anche essere chiamato da un altro thread, ma complicherebbe inutilmente il diagramma.

join-calling dovrebbe essere inserito nella traccia del thread principale, ma per esprimere la relazione thread e mantenerlo il più semplice possibile, scelgo invece di inserirlo nel thread figlio.

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

Quindi il motivo per cui non vedi alcuna modifica è perché il tuo thread principale non fa nulla dopo il tuo join. Si potrebbe dire che joinè (solo) rilevante per il flusso di esecuzione del thread principale.

Se, ad esempio, si desidera scaricare contemporaneamente un gruppo di pagine per concatenarle in un'unica pagina di grandi dimensioni, è possibile avviare download simultanei utilizzando i thread, ma è necessario attendere fino al termine dell'ultima pagina / thread prima di iniziare a assemblare una singola pagina di molti. Questo è quando usi join().


Conferma che un thread demone può essere unito () senza bloccare l'esecuzione del programma?
Aviator45003

@ Aviator45003: Sì, utilizzando l'argomento timeout come: demon_thread.join(0.0), join()è per default il blocco senza riguardo per l'attributo daemonized. Ma unirsi a un filo demonizzato apre molto probabilmente un sacco di problemi! Ora sto considerando di rimuovere la join()chiamata nel mio piccolo diagramma per il thread demone ...
Don Domanda

@DonQuestion Quindi, se ci accediamo daemon=True, non abbiamo bisogno di join()se abbiamo bisogno join()alla fine del codice?
Benyamin Jafari il

@BenyaminJafari: Sì. In caso contrario, il thread principale (= programma) uscirà, se rimane solo il thread demone. Ma la natura di un thread demone (python) è che al thread principale non importa se questa attività in background è ancora in esecuzione. Penserò a come approfondirlo nella mia risposta, per chiarire la questione. Grazie per il tuo commento!
Don Domanda

Nel primo caso, al main threadtermine, il programma finirà senza lasciar child-thread(long)terminare l'esecuzione (ovvero child-thread(long)non è completamente completato)?
skytree,

65

Direttamente dai documenti

join ([timeout]) Attendi il termine del thread. Questo blocca il thread chiamante fino a quando termina il thread il cui metodo join () viene chiamato - normalmente o tramite un'eccezione non gestita - o fino al timeout opzionale.

Ciò significa che il thread principale che viene generato te dattende tdi terminare fino al termine.

A seconda della logica utilizzata dal programma, è possibile che si desideri attendere il completamento di un thread prima che il thread principale continui.

Anche dai documenti:

Un thread può essere contrassegnato come "thread demone". Il significato di questo flag è che l'intero programma Python si chiude quando rimangono solo thread daemon.

Un semplice esempio, diciamo che abbiamo questo:

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

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

t.start()

Che termina con:

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

Questo produrrà:

Test one
Test non-daemon
Test two

Qui il thread principale attende esplicitamente che il tthread termini fino a quando non chiama printla seconda volta.

In alternativa, se avessimo questo:

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

Otterremo questo risultato:

Test one
Test two
Test non-daemon

Qui facciamo il nostro lavoro nel thread principale e quindi aspettiamo che il tthread termini. In questo caso potremmo anche rimuovere il join esplicito t.join()e il programma attenderà implicitamente il tcompletamento.


Puoi fare qualche chnage al mio codice in modo che io possa vedere la differenza di t.join(). aggiungendo soome sleep o qualcos'altro. al momento riesco a vedere qualsiasi chnage nel programma anche se lo uso o meno. ma per damemon posso vedere la sua uscita se uso d.join()ciò che non vedo quando non uso d.join ()
user192362127

34

Grazie per questa discussione - mi ha aiutato molto.

Oggi ho imparato qualcosa su .join ().

Questi thread funzionano in parallelo:

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

e questi funzionano in sequenza (non quello che volevo):

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

In particolare, stavo cercando di fare in modo intelligente e ordinato:

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

Questo funziona! Ma funziona in sequenza. Posso mettere self.start () in __ init __, ma non self.join (). Questo deve essere fatto dopo che ogni thread è stato avviato.

join () è ciò che fa sì che il thread principale attenda il termine del thread. Altrimenti, il tuo thread viene eseguito da solo.

Quindi un modo per pensare a join () come una "trattenuta" sul thread principale: in qualche modo de-thread il thread e viene eseguito in sequenza nel thread principale, prima che il thread principale possa continuare. Assicura che il thread sia completo prima che il thread principale si sposti in avanti. Nota che questo significa che va bene se il tuo thread è già finito prima di chiamare il join () - il thread principale viene semplicemente rilasciato immediatamente quando viene chiamato join ().

In effetti, mi viene subito in mente che il thread principale attende d.join () fino a quando il thread d non termina prima di passare a t.join ().

In effetti, per essere molto chiari, considera questo codice:

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"

Produce questo output (notare come le istruzioni di stampa sono intrecciate l'una nell'altra).

$ 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 () sta sostenendo il thread principale. Tutti e tre i thread vengono completati prima che t1.join () termini e il thread principale si sposta per eseguire la stampa, quindi t2.join (), quindi stampa, quindi t3.join (), quindi stampa.

Correzioni benvenute. Sono anche nuovo al threading.

(Nota: nel caso tu sia interessato, sto scrivendo il codice per un DrinkBot e ho bisogno del threading per eseguire contemporaneamente le pompe degli ingredienti piuttosto che in sequenza - meno tempo di attesa per ogni bevanda.)


Ciao, sono anche nuovo al threading Python e confuso sul thread principale, Il primo thread è il thread principale, In caso contrario, per favore guidami?
Rohit Khatri l'

Il thread principale è il programma stesso. Ciascuno dei thread viene biforcuto da lì. Vengono quindi uniti di nuovo, perché al comando join (), il programma attende che il thread sia terminato prima di continuare l'esecuzione.
Kiki Jewell,

15

Il metodo join ()

blocca il thread chiamante fino al termine del thread il cui metodo join () viene chiamato.

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


14
quindi a che serve join? vedi la domanda di OP, non solo parafrasare la documentazione
Don Domanda del

@DonQuestion ho anche provato ad aggiungere sleep.timer (20) nel thread non daemon senza utilizzare t.join()e il programma lo attende ancora prima della chiusura. non vedo alcun uso t.join()qui nel mio codice
user192362127,

vedi la mia risposta, per ulteriori spiegazioni. per quanto riguarda il tuo sleep.timer in non demone -> un thread demone viene disaccoppiato della durata del thread padre e quindi i thread parent / fratello non saranno influenzati dalla durata del thread demonizzato e viceversa .
Domanda Don

2
La terminologia 'join' e 'block' è sconcertante. 'Bloccato' suggerisce che il processo di chiamata è 'bloccato' dal fare un numero qualsiasi di cose che deve ancora fare, mentre in realtà è appena bloccato dal termine (ritorno al sistema operativo), non di più. Allo stesso modo, non è così ovvio che esiste un thread principale che chiama un thread figlio per "unirlo" (ovvero terminare). Quindi, don Q, grazie per la spiegazione.
RolfBly,

5

Comprensione semplice,

con join, l'interprete attenderà il completamento o la conclusione del processo

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

senza join: l'interprete non attenderà il termine del processo ,

>>> t = Thread(target=sam)
>>> t.start()
started
>>> print 'yes done' #without join interpreter wont wait until process get terminated
yes done
>>> waiting for 10sec

1

Quando si fa join(t)funzione sia per il thread non daemon che per il thread demone, il thread principale (o processo principale) dovrebbe attendere tqualche secondo, quindi può andare oltre a lavorare sul proprio processo. Durante i tsecondi di attesa, entrambi i thread figlio dovrebbero fare ciò che possono fare, come stampare del testo. Dopo tpochi secondi, se il thread non daemon non ha ancora terminato il suo lavoro, e può ancora completarlo dopo che il processo principale ha terminato il suo lavoro, ma per il thread demone, ha perso la finestra delle opportunità. Tuttavia, alla fine morirà dopo la chiusura del programma Python. Per favore, correggimi se c'è qualcosa che non va.


1

In python 3.x join () viene utilizzato per unire un thread con il thread principale, ovvero quando viene utilizzato join () per un thread specifico, il thread principale interromperà l'esecuzione fino al completamento dell'esecuzione del thread unito.

#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! 

'''

0

Questo esempio dimostra l' .join()azione:

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

Su:

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

0

Esistono alcuni motivi per cui il thread principale (o qualsiasi altro thread) si unisce ad altri thread

  1. Un thread potrebbe aver creato o conservato (bloccato) alcune risorse. Il thread di chiamata di join potrebbe essere in grado di cancellare le risorse per suo conto

  2. join () è una chiamata di blocco naturale affinché il thread di chiamata di join continui dopo che il thread chiamato è terminato.

Se un programma python non unisce altri thread, l'interprete python si unirà comunque ai thread non daemon per suo conto.


-2

"A che serve usare join ()?" tu dici. Davvero, è la stessa risposta di "a che cosa serve chiudere i file, poiché python e il sistema operativo chiuderanno il mio file per me quando il mio programma esce?".

È semplicemente una buona programmazione. Dovresti unire () i tuoi thread nel punto in cui il thread non dovrebbe più essere in esecuzione, sia perché devi assicurarti che il thread non sia in esecuzione per interferire con il tuo codice, sia che desideri comportarti correttamente in un sistema più grande.

Potresti dire "Non voglio che il mio codice tarda a dare una risposta" solo a causa del tempo aggiuntivo che il join () potrebbe richiedere. Questo può essere perfettamente valido in alcuni scenari, ma ora è necessario tenere conto del fatto che il codice "sta lasciando in giro per Python e il sistema operativo per ripulire". Se lo fai per motivi di prestazioni, ti incoraggio vivamente a documentare quel comportamento. Ciò è particolarmente vero se stai costruendo una libreria / pacchetto che altri dovrebbero utilizzare.

Non c'è motivo di non aderire (), diversi motivi di prestazioni, e direi che il codice non ha bisogno di svolgere quel bene.


6
Quello che dici sulla pulizia dei fili non è vero. Dai un'occhiata al codice sorgente di threading.Thread.join (). Tutto ciò che fa è attendere un blocco e poi tornare. Nulla è effettivamente ripulito.
Collin,

1
@Collin - Il thread stesso potrebbe contenere risorse, in tal caso l'interprete e il sistema operativo dovranno effettivamente ripulire "cruft".
qneill

1
Ancora una volta, guarda il codice sorgente di threading.Thread.join (). Non c'è nulla che scateni la raccolta di risorse.
Collin

Non è necessariamente (e come dici tu, per niente) il modulo di threading che contiene risorse, ma il thread stesso. L'uso di join () significa che stai aspettando che il thread finisca di fare ciò che voleva fare, che potrebbe includere l'allocazione e il rilascio di risorse.
Chris Cogdon,

2
L'eventuale attesa o meno non influisce sul rilascio delle risorse trattenute dal thread. Non sono sicuro del perché tu lo stia collegando con la chiamata join().
Collin,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.