Por que sys.exit () não sai quando chamado dentro de um thread em Python?

98

Esta pode ser uma pergunta estúpida, mas estou testando algumas de minhas suposições sobre Python e estou confuso sobre por que o seguinte trecho de código não sairia quando chamado no thread, mas sairia quando chamado no thread principal.

import sys, time
from threading import Thread

def testexit():
    time.sleep(5)
    sys.exit()
    print "post thread exit"

t = Thread(target = testexit)
t.start()
t.join()
print "pre main exit, post thread exit"
sys.exit()
print "post main exit"

Os documentos para sys.exit () afirmam que a chamada deve sair do Python. Eu posso ver na saída deste programa que "post thread exit" nunca é impresso, mas o thread principal continua mesmo após a saída das chamadas de thread.

Uma instância separada do interpretador está sendo criada para cada thread e a chamada para exit () está apenas saindo dessa instância separada? Em caso afirmativo, como a implementação de encadeamento gerencia o acesso a recursos compartilhados? E se eu quisesse sair do programa do thread (não que eu realmente queira, mas apenas para que eu entenda)?

Shabbyrobe
fonte

Respostas:

71

sys.exit()levanta a SystemExitexceção, assim como thread.exit(). Portanto, quando sys.exit()gera essa exceção dentro desse encadeamento, tem o mesmo efeito que chamar thread.exit(), e é por isso que apenas o encadeamento sai.

rpkelly
fonte
23

E se eu quiser sair do programa do thread?

Além do método descrito por Deestan, você pode chamar os._exit(observe o sublinhado). Antes de usá-lo certifique-se que você entenda que ele faz não limpezas (como chamar __del__ou similar).

Helmut Grohne
fonte
2
Ele irá liberar o I / O?
Lorenzo Belli
1
os._exit (n): "Saia do processo com status n, sem chamar manipuladores de limpeza, esvaziando buffers stdio, etc."
Tim Richardson
Observe que quando os._exité usado dentro de curses, o console não é redefinido para um estado normal com isso. Você tem que executar resetno shell do Unix para corrigir isso.
sjngm
22

E se eu quisesse sair do programa do thread (não que eu realmente queira, mas apenas para que eu entenda)?

Pelo menos no Linux você pode fazer:

os.kill(os.getpid(), signal.SIGINT)

Isso envia um SIGINTpara o tópico principal que gera a KeyboardInterrupt. Com isso, você tem uma limpeza adequada. Você pode até mesmo controlar o sinal, se necessário.

BTW: No Windows, você só pode enviar um SIGTERMsinal, que não pode ser capturado pelo Python. Nesse caso, você pode simplesmente usar os._exitcom o mesmo efeito.

Chris
fonte
1
Também funciona com maldições, ao contrário de os._exit
sjngm
12

É o fato de que "pré saída principal, pós saída do tópico" é impresso o que está incomodando você?

Ao contrário de algumas outras linguagens (como Java) em que o analógico sys.exit( System.exit, no caso de Java) faz com que a VM / processo / interpretador pare imediatamente, o Python sys.exitapenas lança uma exceção: uma exceção SystemExit em particular.

Aqui estão os documentos para sys.exit(apenas print sys.exit.__doc__):

Saia do interpretador aumentando SystemExit (status).
Se o status for omitido ou Nenhum, o padrão é zero (ou seja, sucesso).
Se o status for numérico, ele será usado como o status de saída do sistema.
Se for outro tipo de objeto, ele será impresso e o
status de saída do sistema será um (ou seja, falha).

Isso tem algumas consequências:

  • em um encadeamento, ele apenas elimina o encadeamento atual, não todo o processo (supondo que chegue ao topo da pilha ...)
  • os destruidores de objetos ( __del__) são potencialmente invocados quando os stack frames que fazem referência a esses objetos são desenrolados
  • finalmente os blocos são executados conforme a pilha se desenrola
  • você pode pegar uma SystemExitexceção

O último é possivelmente o mais surpreendente e é mais um motivo pelo qual você quase nunca deve ter uma exceptinstrução não qualificada em seu código Python.

Laurence Gonsalves
fonte
11

E se eu quisesse sair do programa do thread (não que eu realmente queira, mas apenas para que eu entenda)?

Meu método preferido é a passagem de mensagens tipo Erlang. Levemente simplificado, eu faço assim:

import sys, time
import threading
import Queue # thread-safe

class CleanExit:
  pass

ipq = Queue.Queue()

def testexit(ipq):
  time.sleep(5)
  ipq.put(CleanExit)
  return

threading.Thread(target=testexit, args=(ipq,)).start()
while True:
  print "Working..."
  time.sleep(1)
  try:
    if ipq.get_nowait() == CleanExit:
      sys.exit()
  except Queue.Empty:
    pass
Deestan
fonte
3
Você não precisa de um Queueaqui. Apenas um simples boolbastará. O nome clássico dessa variável é is_activee seu valor padrão inicial é True.
Acumenus de
3
Sim você está correto. De acordo com effbot.org/zone/thread-synchronization.htm , modificar um bool(ou qualquer outra operação atômica) funcionará perfeitamente para este problema específico. A razão de eu ir com Queues é que quando se trabalha com agentes de rosca I tendem a acabar precisando de vários sinais diferentes ( flush, reconnect, exit, etc ...) quase imediatamente.
Deestan