Qual é o uso de join () no encadeamento de Python?

197

Eu estava estudando a segmentação de python e me deparei join().

O autor disse que, se o encadeamento estiver no modo daemon, preciso usar join()para que o encadeamento possa terminar antes que o encadeamento principal seja encerrado.

mas eu também o vi usando t.join()mesmo que tnão fossedaemon

código de exemplo é este

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

não sei de que serve, t.join()pois não é daemon e não consigo ver nenhuma alteração, mesmo que eu o remova

user192362127
fonte
13
+1 para o título. O 'ingresso' parece ter sido especialmente projetado para incentivar o fraco desempenho (criando / encerrando / destruindo continuamente threads), bloqueios da GUI (aguardando nos manipuladores de eventos) e falhas no desligamento do aplicativo (aguardando o término dos threads ininterruptos). Nota - não apenas Python, este é um antipadrão entre idiomas.
Martin James

Respostas:

287

Uma arte um tanto desajeitada para demonstrar o mecanismo: join()presumivelmente é chamado pelo thread principal. Também poderia ser chamado por outro thread, mas complicaria desnecessariamente o diagrama.

join-calling deve ser colocado na trilha do thread principal, mas para expressar a relação do thread e mantê-lo o mais simples possível, eu escolho colocá-lo no thread filho.

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

Portanto, o motivo pelo qual você não vê nenhuma alteração é porque o thread principal não faz nada depois do seu join. Você poderia dizer que joiné (apenas) relevante para o fluxo de execução do thread principal.

Se, por exemplo, você desejar baixar simultaneamente várias páginas para concatená-las em uma única página grande, inicie downloads simultâneos usando threads, mas precisará aguardar até que a última página / thread seja concluída antes de começar a montar uma única página de muitos. É quando você usa join().

Don Question
fonte
Por favor, confirme se um thread daemonizado pode ser unido () sem bloquear a execução do programa?
precisa saber é o seguinte
@ Aviator45003: Sim, usando o argumento de tempo limite como: demon_thread.join(0.0), join()é, por padrão o bloqueio sem levar em conta o atributo daemon. Mas ingressar em um segmento demonizado abre muito provavelmente uma lata de problemas! Agora estou considerando a possibilidade de remover a join()chamada no meu pequeno diagrama para o daemon-thread ...
Don Pergunta
@ DonQuestion Então, se começarmos daemon=True, não precisamos do join()que precisamos join()no final do código?
Benyamin Jafari
@BenyaminJafari: Sim. Caso contrário, o thread principal (= programa) sairia, se restasse apenas o daemon-thread. Mas a natureza de um encadeamento de daemon (python) é que o encadeamento principal não se importa se essa tarefa em segundo plano ainda está em execução. Vou pensar em como elaborar isso na minha resposta, para esclarecer essa questão. Obrigado por seu comentário!
Don Pergunta
No primeiro caso, quando a main threadfinalização, o programa será finalizado sem deixar que a child-thread(long)execução seja executada por si mesma (ou child-thread(long)seja, não foi completamente concluída)?
skytree
65

Diretamente dos documentos

join ([timeout]) Aguarde até o encadeamento terminar. Isso bloqueia o encadeamento de chamada até que o encadeamento cujo método join () é chamado termine - normalmente ou por meio de uma exceção não tratada - ou até que ocorra o tempo limite opcional.

Isso significa que a linha principal que gera te daguarda o ttérmino até terminar.

Dependendo da lógica empregada pelo seu programa, você pode esperar até que um encadeamento seja concluído antes que o encadeamento principal continue.

Também a partir dos documentos:

Um encadeamento pode ser sinalizado como um "encadeamento daemon". O significado desse sinalizador é que todo o programa Python sai quando apenas os threads do daemon são deixados.

Um exemplo simples, digamos que temos o seguinte:

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

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

t.start()

Que termina com:

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

Isso produzirá:

Test one
Test non-daemon
Test two

Aqui, o encadeamento mestre espera explicitamente que o tencadeamento seja concluído até que seja chamado printpela segunda vez.

Como alternativa, se tivéssemos isso:

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

Obteremos esta saída:

Test one
Test two
Test non-daemon

Aqui, realizamos nosso trabalho no thread principal e aguardamos o ttérmino do thread. Nesse caso, podemos até remover a junção explícita t.join()e o programa aguardará implicitamente ta conclusão.

dmg
fonte
Você pode fazer algum chnage no meu código para que eu possa ver a diferença de t.join(). adicionando sono profundo ou algo mais. No momento, posso ver qualquer alteração no programa, mesmo que eu o use ou não. mas para damemon eu posso ver sua saída se eu usar d.join()o que eu não ver quando eu não uso d.join ()
user192362127
34

Obrigado por esta discussão - também me ajudou muito.

Eu aprendi algo sobre .join () hoje.

Esses threads são executados em paralelo:

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

e estes são executados sequencialmente (não o que eu queria):

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

Em particular, eu estava tentando ser inteligente e arrumado:

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

Isso funciona! Mas é executado seqüencialmente. Eu posso colocar o self.start () em __ init __, mas não o self.join (). Isso tem que ser feito depois cada encadeamento.

join () é o que faz com que o thread principal aguarde o término do thread. Caso contrário, seu encadeamento será executado sozinho.

Portanto, uma maneira de pensar em join () como um "hold" no thread principal - meio que desassocia seu thread e é executado sequencialmente no thread principal, antes que o thread principal possa continuar. Assegura que seu encadeamento esteja completo antes que o encadeamento principal avance. Observe que isso significa que está tudo bem se o seu encadeamento já estiver concluído antes de você chamar o join () - o encadeamento principal é liberado imediatamente quando o join () é chamado.

De fato, agora me ocorre que o thread principal aguarda em d.join () até o thread d terminar antes de passar para t.join ().

De fato, para ser bem claro, considere este código:

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"

Produz essa saída (observe como as instruções de impressão são encadeadas uma na outra.)

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

O t1.join () está mantendo o encadeamento principal. Todos os três encadeamentos são concluídos antes que o t1.join () termine e o encadeamento principal prossiga para executar a impressão, depois t2.join () e, em seguida, imprima e t3.join () e depois imprima.

Correções são bem-vindas. Eu também sou novo no segmento.

(Observação: caso você esteja interessado, estou escrevendo um código para um DrinkBot e preciso executar rosqueamentos para executar as bombas de ingredientes simultaneamente e não sequencialmente - menos tempo para aguardar cada bebida.)

Kiki Jewell
fonte
Ei, eu também sou novo em Python Threading e confuso sobre o segmento principal. O primeiro segmento é o segmento principal. Se não, por favor, me guie?
Rohit Khatri
O segmento principal é o próprio programa. Cada um dos segmentos é bifurcado a partir daí. Eles são reunidos novamente - porque no comando join (), o programa espera até que o encadeamento seja concluído antes de continuar a execução.
Kiki Jewell
15

O método join ()

bloqueia o encadeamento de chamada até que o encadeamento cujo método join () é chamado seja encerrado.

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

Ketouem
fonte
14
então, qual é o uso de join? veja a pergunta do OP, não parafraseie apenas os documentos
Don Question
@DonQuestion Eu até tentei adicionar sleep.timer (20) em thread não daemon sem usar t.join()e o programa ainda o espera antes do término. Eu não vejo nenhum uso t.join()aqui no meu código
user192362127
veja minha resposta, para mais explicações. em relação ao sleep.timer em não-demônio -> um demônio é dissociado da vida útil do seu segmento pai e, portanto, os segmentos pai / irmão não serão afetados pela vida útil do segmento demonizado e vice-versa .
Don Pergunta
2
A terminologia 'join' e 'block' é intrigante. 'Bloqueado' sugere que o processo de chamada está 'impedido' de fazer qualquer número de coisas que ainda precisa fazer, enquanto, na verdade, está impedido de terminar (retornar ao sistema operacional), não mais. Da mesma forma, não é tão óbvio que haja um thread principal chamando um thread filho para 'ingressar' nele (ou seja, encerrar). Então, Don Q, obrigado pela explicação.
precisa saber é o seguinte
5

Compreensão simples,

with join - o intérprete aguardará até que seu processo seja concluído ou encerrado

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

sem ingresso - o intérprete não espera até que o processo seja encerrado ,

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

Ao criar join(t)funções para encadeamentos não daemon e encadeamento daemon, o encadeamento principal (ou processo principal) deve aguardar tsegundos e pode ir além para trabalhar em seu próprio processo. Durante os tsegundos de espera, os dois segmentos filhos devem fazer o que podem, como imprimir algum texto. Após os tsegundos, se o encadeamento que não é daemon ainda não terminou seu trabalho, e ainda pode finalizá-lo após o processo principal concluir seu trabalho, mas para o encadeamento daemon, apenas perdeu a janela da oportunidade. No entanto, acabará por morrer após a saída do programa python. Por favor, corrija-me se houver algo errado.

user1342336
fonte
1

No python 3.x, join () é usado para ingressar em um encadeamento com o encadeamento principal, ou seja, quando join () é usado para um encadeamento específico, o encadeamento principal para de ser executado até que a execução do encadeamento unido seja concluída.

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

Este exemplo demonstra a .join()ação:

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

Fora:

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

Existem alguns motivos para o thread principal (ou qualquer outro thread) ingressar em outros threads

  1. Um encadeamento pode ter criado ou retido (bloqueado) alguns recursos. O encadeamento de chamada de associação pode conseguir limpar os recursos em seu nome

  2. join () é uma chamada de bloqueio natural para o encadeamento de chamada de junção continuar após o término do encadeamento chamado.

Se um programa python não ingressar em outros threads, o interpretador python ainda ingressará em threads não daemon em seu nome.

yoonghm
fonte
-2

"Qual é a utilidade de usar join ()?" você diz. Realmente, é a mesma resposta que "para que serve o fechamento de arquivos, já que o python e o sistema operacional fecharão meu arquivo para mim quando o programa terminar?".

É simplesmente uma questão de boa programação. Você deve juntar () seus tópicos no ponto no código em que o tópico não deve ser executado, porque você deve garantir positivamente que o thread não esteja em execução para interferir com seu próprio código ou se deseja se comportar corretamente em um sistema maior.

Você pode dizer "Não quero que meu código atrase a resposta" apenas por causa do tempo adicional que o join () pode exigir. Isso pode ser perfeitamente válido em alguns cenários, mas agora você precisa levar em consideração que seu código está "deixando um problema para que o python e o sistema operacional sejam limpos". Se você fizer isso por razões de desempenho, recomendo fortemente que você documente esse comportamento. Isto é especialmente verdade se você estiver construindo uma biblioteca / pacote que outros deverão utilizar.

Não há motivo para não entrar no (), além dos motivos de desempenho, e eu argumentaria que seu código não precisa ter um desempenho tão bom.

Chris Cogdon
fonte
6
O que você diz sobre a limpeza de threads não é verdade. Dê uma olhada no código fonte de threading.Thread.join (). Tudo o que essa função faz é aguardar um bloqueio e retornar. Nada é realmente limpo.
Collin
1
@ Collin - O encadeamento em si pode conter recursos; nesse caso, o intérprete e o sistema operacional precisarão realmente limpar o "cruft".
Qneill
1
Mais uma vez, observe o código fonte de threading.Thread.join (). Não há nada lá que desencadeie a coleta de recursos.
Collin
Não é necessariamente (e como você diz, nem um pouco) o módulo de encadeamento que está retendo recursos, mas o próprio encadeamento. Usar join () significa que você está aguardando o thread concluir o que queria, o que pode incluir a alocação e liberação de recursos.
Chris Cogdon
2
Esperar ou não, não afeta quando os recursos mantidos pelo encadeamento são liberados. Não sei por que você está ligando isso ao ligar join().
Collin