O que exatamente o método .join () do módulo de multiprocessamento Python está fazendo?

110

Aprendendo sobre multiprocessamento Python (de um artigo PMOTW ) e adoraria alguns esclarecimentos sobre o que exatamente o join()método está fazendo.

Em um antigo tutorial de 2008 afirma que sem a p.join()chamada no código abaixo, "o processo filho ficará ocioso e não será encerrado, tornando-se um zumbi que você deve matar manualmente".

from multiprocessing import Process

def say_hello(name='world'):
    print "Hello, %s" % name

p = Process(target=say_hello)
p.start()
p.join()

Eu adicionei uma impressão do PIDe também um time.sleeppara testar e, pelo que eu posso dizer, o processo termina sozinho:

from multiprocessing import Process
import sys
import time

def say_hello(name='world'):
    print "Hello, %s" % name
    print 'Starting:', p.name, p.pid
    sys.stdout.flush()
    print 'Exiting :', p.name, p.pid
    sys.stdout.flush()
    time.sleep(20)

p = Process(target=say_hello)
p.start()
# no p.join()

dentro de 20 segundos:

936 ttys000    0:00.05 /Library/Frameworks/Python.framework/Versions/2.7/Reso
938 ttys000    0:00.00 /Library/Frameworks/Python.framework/Versions/2.7/Reso
947 ttys001    0:00.13 -bash

após 20 segundos:

947 ttys001    0:00.13 -bash

O comportamento é o mesmo com p.join()adicionado de volta no final do arquivo. O Módulo Python da Semana oferece uma explicação muito legível do módulo ; "Para esperar até que um processo conclua seu trabalho e saia, use o método join ().", Mas parece que pelo menos o OS X estava fazendo isso de qualquer maneira.

Também estou me perguntando sobre o nome do método. O .join()método está concatenando alguma coisa aqui? É concatenar um processo com seu fim? Ou ele apenas compartilha um nome com o .join()método nativo do Python ?

MikeiLL
fonte
2
até onde eu sei, ele mantém o encadeamento principal e espera que o processo filho seja concluído e, em seguida, reúne os recursos no encadeamento principal, principalmente faz uma saída limpa.
abhishekgarg
ah isso faz sentido. Portanto, os reais CPU, Memory resourcesestão sendo separados do processo pai e, em seguida, joinremovidos novamente após a conclusão do processo filho?
MikeiLL
sim, é isso que está fazendo. Então, se você não juntá-los de volta, quando o processo filho for concluído, ele ficará como um processo extinto ou morto
abhishekgarg
@abhishekgarg Isso não é verdade. Os processos filho serão associados implicitamente quando o processo principal for concluído.
dano de
@dano, também estou aprendendo python e acabei de compartilhar o que descobri em meus testes, em meus testes eu tinha um processo principal sem fim, então talvez seja por isso que eu vi esses processos filhos como extintos.
abhishekgarg

Respostas:

125

O join()método, quando usado com threadingou multiprocessing, não está relacionado a str.join()- não está realmente concatenando nada. Em vez disso, significa apenas "esperar que este [thread / processo] seja concluído". O nome joiné usado porque a multiprocessingAPI do módulo deve ser semelhante à threadingAPI do threadingmódulo e o módulo usa joinpara seu Threadobjeto. Usando o termojoin para significar "esperar a conclusão de um thread" é ​​comum em muitas linguagens de programação, portanto, o Python também o adotou.

Agora, o motivo pelo qual você vê o atraso de 20 segundos com e sem a chamada de join()é porque, por padrão, quando o processo principal está pronto para sair, ele implicitamente chamará join()todas as multiprocessing.Processinstâncias em execução . Isso não está tão claramente declarado nos multiprocessingdocumentos como deveria, mas é mencionado na seção Diretrizes de programação :

Lembre-se também de que os processos não demoníacos serão automaticamente unidos.

Você pode substituir esse comportamento definindo a daemonbandeira no Processque Trueantes de se iniciar o processo:

p = Process(target=say_hello)
p.daemon = True
p.start()
# Both parent and child will exit here, since the main process has completed.

Se você fizer isso, o processo filho será encerrado assim que o processo principal for concluído :

demônio

O sinalizador do daemon do processo, um valor booleano. Isso deve ser definido antes de start () ser chamado.

O valor inicial é herdado do processo de criação.

Quando um processo é encerrado, ele tenta encerrar todos os seus processos filho daemônicos.

dano
fonte
6
Eu estava entendendo que p.daemon=Trueera para "iniciar um processo em segundo plano que é executado sem bloquear a saída do programa principal". Mas se "O processo daemon é encerrado automaticamente antes que o programa principal seja encerrado", qual é exatamente o seu uso?
MikeiLL
8
@MikeiLL Basicamente, qualquer coisa que você queira que aconteça em segundo plano enquanto o processo pai estiver em execução, mas isso não precisa ser limpo normalmente antes de sair do programa principal. Talvez um processo de trabalho que leia dados de um soquete ou dispositivo de hardware e os envie de volta para o pai por meio de uma fila ou os processe em segundo plano para alguma finalidade? Em geral, eu diria que usar um daemonicprocesso filho não é muito seguro, porque o processo será encerrado sem permitir a limpeza de quaisquer recursos abertos que possa ter .. (cont.).
dano
7
@MikeiLL Uma prática melhor seria sinalizar para a criança para limpar e sair antes de sair do processo principal. Você pode pensar que faria sentido deixar o processo filho daemônico em execução quando o pai sair, mas lembre-se de que a multiprocessingAPI foi projetada para imitar a threadingAPI o mais próximo possível. Os threading.Threadobjetos demoníacos são encerrados assim que o thread principal termina, portanto, os multiprocesing.Processobjetos demoníacos se comportam da mesma maneira.
dano
38

Sem o join(), o processo principal pode ser concluído antes do processo filho. Não tenho certeza sob quais circunstâncias isso leva ao zumbi.

O objetivo principal de join()é garantir que um processo filho seja concluído antes que o processo principal faça qualquer coisa que dependa do trabalho do processo filho.

A etimologia de join()é que é o oposto de fork, que é o termo comum nos sistemas operacionais da família Unix para criar processos filhos. Um único processo se "bifurca" em vários e depois "se junta" novamente em um.

Russell Borogove
fonte
2
Ele usa o nome join()porque join()é o que é usado para esperar threading.Threada conclusão de um objeto, e a multiprocessingAPI foi criada para imitar a threadingAPI tanto quanto possível.
dano de
Sua segunda declaração aborda o problema com o qual estou lidando em um projeto atual.
MikeiLL
Eu entendo a parte em que o thread principal espera a conclusão do subprocesso, mas isso não anula o propósito da execução assíncrona? Não é para terminar a execução, de forma independente (a subtarefa ou processo)?
Apurva Kunkulol
1
@ApurvaKunkulol Depende de como você está usando, mas join()é necessário no caso em que a thread principal precisa dos resultados do trabalho das sub-threads. Por exemplo, se você estiver renderizando algo e atribuir 1/4 da imagem final a cada um dos 4 subprocessos, e quiser exibir a imagem inteira quando terminar.
Russell Borogove
@RussellBorogove Ah! Entendi. Então, o significado da atividade assíncrona é um pouco diferente aqui. Deve significar apenas o fato de que os subprocessos devem realizar suas tarefas simultaneamente com o thread principal, enquanto o thread principal também faz o seu trabalho, em vez de apenas esperar ociosamente os subprocessos.
Apurva Kunkulol
12

Não vou explicar em detalhes o que joinsignifica, mas aqui está a etimologia e a intuição por trás disso, que deve ajudá-lo a se lembrar de seu significado com mais facilidade.

A ideia é que a execução se " bifurque " em vários processos, dos quais um é o mestre e os demais são trabalhadores (ou "escravos"). Quando os trabalhadores terminam, eles "unem" o mestre para que a execução em série possa ser retomada.

O joinmétodo faz com que o processo mestre espere que um trabalhador se junte a ele. O método poderia ter sido chamado melhor de "espera", já que esse é o comportamento real que ele causa no mestre (e é isso que é chamado no POSIX, embora os threads do POSIX o chamem de "junção" também). A junção ocorre apenas como um efeito dos threads cooperando adequadamente, não é algo que o mestre faz .

Os nomes "fork" e "join" têm sido usados ​​com este significado no multiprocessamento desde 1963 .

larsmans
fonte
Portanto, de certa forma, esse uso da palavra joinpode ter precedido seu uso para se referir à concatenação, ao contrário do contrário.
MikeiLL
1
É improvável que o uso em concatenação seja derivado do uso em multiprocessamento; ao contrário, ambos os sentidos derivam separadamente do sentido da palavra em inglês simples.
Russell Borogove
2

join()é usado para aguardar a saída dos processos de trabalho. É preciso ligar close()ou terminate()antes de usar join().

Como @Russell, a junção mencionada é como o oposto de fork (que gera subprocessos).

Para que o join seja executado, você deve executar o close()que impedirá que mais tarefas sejam enviadas ao pool e sairá assim que todas as tarefas forem concluídas. Como alternativa, a execução terminate()apenas será encerrada interrompendo todos os processos de trabalho imediatamente.

"the child process will sit idle and not terminate, becoming a zombie you must manually kill" isso é possível quando o processo principal (pai) sai, mas o processo filho ainda está em execução e, uma vez concluído, não tem nenhum processo pai para o qual retornar seu status de saída.

Ani Menon
fonte
2

o join() chamada garante que as linhas subsequentes do seu código não sejam chamadas antes que todos os processos de multiprocessamento sejam concluídos.

Por exemplo, sem o join(), o código a seguir será chamado restart_program()antes mesmo de os processos terminarem, o que é semelhante ao assíncrono e não é o que queremos (você pode tentar):

num_processes = 5

for i in range(num_processes):
    p = multiprocessing.Process(target=calculate_stuff, args=(i,))
    p.start()
    processes.append(p)
for p in processes:
    p.join() # call to ensure subsequent line (e.g. restart_program) 
             # is not called until all processes finish

restart_program()
Yi Xiang Chong
fonte
0

Para esperar até que um processo conclua seu trabalho e saia, use o método join ().

e

Nota É importante juntar () o processo depois de encerrá-lo para dar tempo ao mecanismo de segundo plano para atualizar o status do objeto para refletir o encerramento.

Este é um bom exemplo que me ajudou a entender: aqui

Uma coisa que notei pessoalmente foi meu processo principal pausado até que a criança terminasse seu processo usando o método join (), o que frustrou o meu objetivo multiprocessing.Process()em primeiro lugar.

Josh
fonte