web-dev-qa-db-fra.com

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

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 fermera pas lorsqu'il est appelé dans le thread, mais quitterait lorsqu'il était 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"

Les documents pour sys.exit () indiquent que l'appel doit quitter Python. Je peux voir à partir de la sortie de ce programme que "la sortie du fil de discussion" n'est jamais imprimée, mais le fil principal continue juste après la fin des appels de fil.

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 du thread gère-t-elle l'accès aux ressources partagées? Que se passe-t-il si je voulais quitter le programme à partir du fil (pas que je le souhaite, mais juste pour que je comprenne)?

76
Shabbyrobe

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

59
rpkelly

Et si je voulais quitter le programme à partir du fil?

Outre la méthode décrite par Deestan, vous pouvez appeler os._exit (remarquez le trait de soulignement). Avant de l'utiliser, assurez-vous que vous comprenez qu'il fait non nettoyages (comme appeler __del__ ou similaire).

20
Helmut Grohne

Que se passe-t-il si je voulais quitter le programme à partir du fil (pas que je le souhaite, mais juste pour que je comprenne)?

Au moins sous Linux, vous pourriez faire quelque chose comme:

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

Cela envoie un SIGINT au thread principal où il peut être traité comme un KeyboardInterrupt.

BTW: Sous Windows, vous ne pouvez envoyer qu'un signal SIGTERM, qui ne peut pas être capturé depuis Python. (là je préfère appeler os._exit comme suggéré par Helmut)

15
Chris

Est-ce le fait que "pré-sortie principale, sortie de fil de discussion" est imprimé qu'est-ce qui vous dérange?

Contrairement à d'autres langages (comme Java) où l'analogue de sys.exit (System.exit, Dans le cas de Java) provoque l'arrêt immédiat de la machine virtuelle/du processus/de l'interpréteur, le sys.exit De Python lance simplement une exception: une exception SystemExit en particulier.

Voici les documents pour sys.exit (Juste print sys.exit.__doc__):

Quittez l'interpréteur en augmentant SystemExit (status).
Si le statut est omis ou Aucun, la valeur par défaut est zéro (c.-à-d. 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 système
l'état de sortie sera un (c.-à-d., échec).

Cela a quelques conséquences:

  • dans un thread, il tue simplement le thread actuel, pas tout le processus (en supposant qu'il arrive jusqu'au sommet de la pile ...)
  • les destructeurs d'objets (__del__) sont potentiellement invoqué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 intercepter une exception SystemExit

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

11

Que se passe-t-il si je voulais quitter le programme à partir du fil (pas que je le souhaite, mais juste pour que je comprenne)?

Ma méthode préférée consiste à passer le message 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
11
Deestan