Nie można zabić skryptu Pythona za pomocą Ctrl-C

117

Testuję wątki Pythona za pomocą następującego skryptu:

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()

Działa w Pythonie 2.7 na Kubuntu 11.10. Ctrl+ Cgo nie zabije. Próbowałem też dodać obsługę sygnałów systemowych, ale to nie pomogło:

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

Aby zabić proces, zabijam go przez PID po wysłaniu programu do tła z Ctrl+ Z, co nie jest ignorowane. Dlaczego Ctrl+ Cjest tak uporczywie ignorowany? Jak mogę to rozwiązać?

dotancohen
źródło
@dotancohen czy to działa w systemie Windows?
kiriloff
@vitaibian: Nie testowałem w systemie Windows, ale wydaje się, że nie jest to specyficzne dla systemu operacyjnego.
dotancohen

Odpowiedzi:

178

Ctrl+ Ckończy główny wątek, ale ponieważ twoje wątki nie są w trybie demona, działają dalej, a to utrzymuje proces przy życiu. Możemy zrobić z nich demony:

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

Ale jest jeszcze jeden problem - gdy główny wątek rozpocznie twoje wątki, nie ma już nic innego do zrobienia. Więc wychodzi, a wątki są natychmiast niszczone. Więc utrzymajmy główny wątek przy życiu:

import time
while True:
    time.sleep(1)

Teraz będzie drukować „pierwszy” i „drugi”, dopóki nie naciśniesz Ctrl+ C.

Edycja: jak zauważyli komentatorzy, wątki demona mogą nie mieć szansy na wyczyszczenie rzeczy, takich jak pliki tymczasowe. Jeśli tego potrzebujesz, złap KeyboardInterruptgłówny wątek i poproś o koordynację czyszczenia i zamykania. Jednak w wielu przypadkach pozwolenie na nagłą śmierć wątków demonów jest prawdopodobnie wystarczające.

Thomas K.
źródło
6
powinieneś wspomnieć, że dzięki temu wątki nie są zatrzymywane z wdziękiem, a niektóre zasoby nie są uwalniane.
Tommaso Barbugli
1
Cóż, Ctrl-C nigdy nie jest wdzięcznym sposobem na zatrzymanie czegokolwiek. Nie jestem pewien, jakie zasoby pozostałyby - czy system operacyjny nie powinien niczego odzyskać po zakończeniu procesu?
Thomas K
7
@ThomasK Pliki tymczasowe utworzone przez tempfile.TemporaryFile()może na przykład pozostać na dysku.
Feuermurmel
1
@ deed02392: Nie wiem dokładnie, co dzieje się z głównym wątkiem, ale o ile wiem, nie możesz z nim nic zrobić po jego zamknięciu. Proces zakończy się, gdy zakończą się wszystkie wątki niebędące demonami; relacje rodzic-dziecko nie wchodzą w to.
Thomas K,
4
Wygląda na to, że w pythonie3 możesz przejśćdaemon=True doThread.__init__
Ryana
3

Myślę, że najlepiej jest wywołać funkcję join () w swoich wątkach, gdy spodziewasz się, że umrą. Pozwoliłem sobie z twoim kodem, aby zakończyć pętle (możesz tam dodać wszystko, czego potrzeba do czyszczenia). Zmienna kostka jest sprawdzana pod kątem prawdy przy każdym przebiegu, a gdy ma wartość True, program kończy działanie.

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
źródło
while Truejest głupie, powinieneś joinwprost - a ta nadpisana funkcja jest trochę wątpliwa. Może def join(self, force=False): if force: self.die = Truetak, że join()nie zmienia się, join(force=True)zabija ich. Ale nawet wtedy lepiej poinformować oba wątki przed dołączeniem do jednego z nich.
o11c
0

Ulepszona wersja odpowiedzi @Thomas K:

  • Zdefiniowanie funkcji asystenta is_any_thread_alive()zgodnie z tym celem , która może main()automatycznie zakończyć działanie .

Przykładowe kody:

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
źródło