Pourquoi sys.exit () ne se ferme-t-il pas lorsqu'il est appelé dans un thread en Python?

98

Cela pourrait être une question stupide, mais je teste certaines de mes hypothèses sur Python et je ne comprends pas pourquoi l'extrait de code suivant ne se ferme pas lorsqu'il est appelé dans le thread, mais se ferme lorsqu'il est appelé dans le 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"

La documentation de sys.exit () indique que l'appel doit quitter Python. Je peux voir à partir de la sortie de ce programme que "post thread exit" n'est jamais imprimé, mais le thread principal continue juste même après que le thread appelle exit.

Une instance distincte de l'interpréteur est-elle créée pour chaque thread et l'appel à exit () quitte simplement cette instance distincte? Si tel est le cas, comment l'implémentation de thread gère-t-elle l'accès aux ressources partagées? Et si je voulais quitter le programme du fil de discussion (pas que je le veuille réellement, mais juste pour que je comprenne)?

Shabbyrobe
la source

Réponses:

71

sys.exit()soulève l' SystemExitexception, tout comme thread.exit(). Ainsi, lorsque sys.exit()déclenche cette exception à l'intérieur de ce thread, cela a le même effet que l'appel thread.exit(), c'est pourquoi seul le thread se ferme.

rpkelly
la source
23

Et si je voulais quitter le programme du fil de discussion?

En dehors de la méthode décrite par Deestan, vous pouvez appeler os._exit(notez le trait de soulignement). Avant de l'utiliser, assurez-vous que vous comprenez qu'il ne fait aucun nettoyage (comme appeler __del__ou similaire).

Helmut Grohne
la source
2
Va-t-il vider les E / S?
Lorenzo Belli
1
os._exit (n): "Quittez le processus avec l'état n, sans appeler les gestionnaires de nettoyage, vider les tampons stdio, etc."
Tim Richardson
Notez que lorsqu'il os._exitest utilisé dans les malédictions, la console n'est pas réinitialisée à un état normal avec cela. Vous devez exécuter resetdans le shell Unix pour résoudre ce problème.
sjngm
22

Et si je voulais quitter le programme du fil de discussion (pas que je le veuille réellement, mais juste pour que je comprenne)?

Au moins sur Linux, vous pouvez faire:

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

Cela envoie un SIGINTau thread principal qui lève un KeyboardInterrupt. Avec cela, vous avez un bon nettoyage. Vous pouvez même gérer le signal si vous le souhaitez.

BTW: Sous Windows, vous ne pouvez envoyer qu'un SIGTERMsignal, qui ne peut pas être capturé depuis Python. Dans ce cas, vous pouvez simplement utiliser os._exitavec le même effet.

Chris
la source
1
Fonctionne
sjngm
12

Est-ce que le fait que «pre main exit, post thread exit» soit imprimé ce qui vous dérange?

Contrairement à certains autres langages (comme Java) où l'analogue à sys.exit( System.exit, dans le cas de Java) provoque l'arrêt immédiat de la VM / processus / interpréteur, Python sys.exitlève simplement une exception: une exception SystemExit en particulier.

Voici la documentation pour sys.exit(juste print sys.exit.__doc__):

Quittez l'interpréteur en levant SystemExit (status).
Si l'état est omis ou Aucun, la valeur par défaut est zéro (c'est-à-dire succès).
Si l'état est numérique, il sera utilisé comme état de sortie du système.
S'il s'agit d'un autre type d'objet, il sera imprimé et le
statut de sortie du système sera un (c'est-à-dire échec).

Cela a quelques conséquences:

  • dans un thread, il tue simplement le thread actuel, pas tout le processus (en supposant qu'il atteigne le sommet de la pile ...)
  • les destructeurs d'objets ( __del__) sont potentiellement appelés lorsque les cadres de pile qui référencent ces objets sont déroulés
  • enfin les blocs sont exécutés lorsque la pile se déroule
  • vous pouvez attraper une SystemExitexception

Le dernier est probablement le plus surprenant, et c'est encore une autre raison pour laquelle vous ne devriez presque jamais avoir une exceptdéclaration non qualifiée dans votre code Python.

Laurence Gonsalves
la source
11

Et si je voulais quitter le programme du fil de discussion (pas que je le veuille réellement, mais juste pour que je comprenne)?

Ma méthode préférée est la transmission de messages Erlang-ish. Légèrement simplifié, je le fais comme ceci:

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
la source
3
Vous n'avez pas besoin d'un Queueici. Juste un simple boolfera très bien. Le nom classique de cette variable est is_activeet sa valeur par défaut initiale est True.
Acumenus
3
Oui vous avez raison. Selon effbot.org/zone/thread-synchronization.htm , modifier une bool(ou toute autre opération atomique) fera parfaitement l'affaire pour ce problème particulier. La raison pour laquelle je vais avec Queues est que lorsque l'on travaille avec des agents filetés j'ai tendance à finir par avoir besoin de plusieurs signaux différents ( flush, reconnect, exit, etc ...) presque immédiatement.
Deestan