Impossible de tuer le script Python avec Ctrl-C

117

Je teste le thread Python avec le script suivant:

import threading

class FirstThread (threading.Thread):
    def run (self):
        while True:
            print 'first'

class SecondThread (threading.Thread):
    def run (self):
        while True:
            print 'second'

FirstThread().start()
SecondThread().start()

Cela fonctionne en Python 2.7 sur Kubuntu 11.10. Ctrl+ Cne le tuera pas. J'ai également essayé d'ajouter un gestionnaire pour les signaux système, mais cela n'a pas aidé:

import signal 
import sys
def signal_handler(signal, frame):
    sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)

Pour tuer le processus, je le tue par PID après avoir envoyé le programme en arrière-plan avec Ctrl+ Z, ce qui n'est pas ignoré. Pourquoi Ctrl+ Cest-il ignoré si constamment? Comment puis-je résoudre ça?

dotancohen
la source
@dotancohen fonctionne-t-il sous Windows?
kiriloff
@vitaibian: Je n'ai pas testé sur Windows, mais cela semble non spécifique au système d'exploitation.
dotancohen

Réponses:

178

Ctrl+ Ctermine le thread principal, mais comme vos threads ne sont pas en mode démon, ils continuent de fonctionner, ce qui maintient le processus en vie. Nous pouvons en faire des démons:

f = FirstThread()
f.daemon = True
f.start()
s = SecondThread()
s.daemon = True
s.start()

Mais il y a un autre problème: une fois que le thread principal a démarré vos threads, il n'y a rien d'autre à faire. Alors ça sort, et les fils sont détruits instantanément. Alors gardons le fil principal en vie:

import time
while True:
    time.sleep(1)

Maintenant, il continuera à imprimer «premier» et «deuxième» jusqu'à ce que vous appuyiez sur Ctrl+ C.

Edit: comme les commentateurs l'ont souligné, les threads du démon peuvent ne pas avoir la possibilité de nettoyer des choses comme les fichiers temporaires. Si vous en avez besoin, attrapez le KeyboardInterruptsur le thread principal et demandez -lui de coordonner le nettoyage et l'arrêt. Mais dans de nombreux cas, laisser les threads démons mourir soudainement est probablement suffisant.

Thomas K
la source
6
vous devez mentionner qu'en faisant cela, les threads ne sont pas arrêtés correctement et certaines ressources ne sont pas libérées.
Tommaso Barbugli
1
Eh bien, Ctrl-C n'est jamais une manière gracieuse d'arrêter quoi que ce soit. Je ne sais pas quelles ressources resteraient - le système d'exploitation ne devrait-il pas récupérer quoi que ce soit lorsque le processus se termine?
Thomas K
7
@ThomasK Les fichiers temporaires créés par tempfile.TemporaryFile()peuvent être laissés sur le disque, par exemple.
Feuermurmel
1
@ deed02392: Je ne sais pas exactement ce qui arrive au thread principal, mais pour autant que je sache, vous ne pouvez rien faire avec après sa sortie. Le processus se terminera lorsque tous les threads non démon seront terminés; les relations parents-enfants n'entrent pas en ligne de compte.
Thomas K
4
On dirait qu'en python3 vous pouvez passerdaemon=True àThread.__init__
Ryan Haining
3

Je pense qu'il est préférable d'appeler join () sur vos threads lorsque vous vous attendez à ce qu'ils meurent. J'ai pris une certaine liberté avec votre code pour que les boucles se terminent (vous pouvez également y ajouter tous les besoins de nettoyage nécessaires). La variable die est vérifiée pour la vérité à chaque passage et quand il est vrai, le programme se termine.

import threading
import time

class MyThread (threading.Thread):
    die = False
    def __init__(self, name):
        threading.Thread.__init__(self)
        self.name = name

    def run (self):
        while not self.die:
            time.sleep(1)
            print (self.name)

    def join(self):
        self.die = True
        super().join()

if __name__ == '__main__':
    f = MyThread('first')
    f.start()
    s = MyThread('second')
    s.start()
    try:
        while True:
            time.sleep(2)
    except KeyboardInterrupt:
        f.join()
        s.join()
Johan Snowgoose
la source
while Trueest idiot, vous devriez joindirectement - et cette fonction surchargée est assez discutable. Peut-être def join(self, force=False): if force: self.die = Trueque cela join()ne change pas en les join(force=True)tue. Mais même dans ce cas, mieux vaut informer les deux threads avant de rejoindre l'un ou l'autre.
o11c
0

Une version améliorée de la réponse de @Thomas K:

  • Définir une fonction d'assistant en fonction is_any_thread_alive()de cet essentiel , qui peut terminer main()automatiquement le.

Exemples de codes:

import threading

def job1():
    ...

def job2():
    ...

def is_any_thread_alive(threads):
    return True in [t.is_alive() for t in threads]

if __name__ == "__main__":
    ...
    t1 = threading.Thread(target=job1,daemon=True)
    t2 = threading.Thread(target=job2,daemon=True)
    t1.start()
    t2.start()

    while is_any_thread_alive([t1,t2]):
        time.sleep(0)
Hansimov
la source