Tidak dapat mematikan skrip Python dengan Ctrl-C

117

Saya menguji threading Python dengan skrip berikut:

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

Ini berjalan dengan Python 2.7 di Kubuntu 11.10. Ctrl+ Ctidak akan membunuhnya. Saya juga mencoba menambahkan penangan untuk sinyal sistem, tetapi itu tidak membantu:

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

Untuk menghentikan proses, saya membunuhnya dengan PID setelah mengirim program ke latar belakang dengan Ctrl+ Z, yang tidak diabaikan. Mengapa Ctrl+ Cterus-menerus diabaikan? Bagaimana cara mengatasi ini?

dotancohen
sumber
@dotancohen apakah ini bekerja pada Windows?
kiriloff
@vitaibian: Saya belum mengujinya di Windows, tetapi tampaknya untuk non-OS tertentu.
dotancohen

Jawaban:

178

Ctrl+ Cmenghentikan utas utama, tetapi karena utas Anda tidak dalam mode daemon, mereka tetap berjalan, dan itu membuat proses tetap hidup. Kita bisa menjadikannya daemon:

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

Tapi kemudian ada masalah lain - setelah utas utama memulai utas Anda, tidak ada lagi yang bisa dilakukan. Jadi itu keluar, dan utasnya langsung hancur. Jadi mari kita pertahankan utas utama:

import time
while True:
    time.sleep(1)

Sekarang itu akan terus mencetak 'pertama' dan 'kedua' sampai Anda menekan Ctrl+ C.

Edit: seperti yang ditunjukkan oleh pemberi komentar, untaian daemon mungkin tidak mendapat kesempatan untuk membersihkan hal-hal seperti file sementara. Jika Anda membutuhkannya, tangkap KeyboardInterruptdi utas utama dan minta dia mengoordinasikan pembersihan dan penghentian. Tetapi dalam banyak kasus, membiarkan utas daemon mati secara tiba-tiba mungkin sudah cukup baik.

Thomas K
sumber
6
Anda harus menyebutkan bahwa dengan melakukan utas ini tidak dihentikan dengan baik dan beberapa sumber daya tidak dirilis.
Tommaso Barbugli
1
Nah, Ctrl-C bukanlah cara yang anggun untuk menghentikan apa pun. Saya tidak yakin sumber daya apa yang tersisa - bukankah seharusnya OS mengambil kembali apa pun saat proses keluar?
Thomas K
7
@ThomasK File sementara yang dibuat oleh tempfile.TemporaryFile()mungkin tertinggal di disk, misalnya.
Feuermurmel
1
@ deed02392: Saya tidak tahu persis apa yang terjadi pada utas utama, tetapi sejauh yang saya tahu Anda tidak dapat melakukan apa pun dengan utas tersebut setelah keluar. Proses ini akan selesai jika semua utas non-daemon telah selesai; hubungan orang tua-anak tidak sampai seperti itu.
Thomas K
4
Sepertinya di python3 Anda bisa lolosdaemon=True keThread.__init__
Ryan Haining
3

Saya pikir yang terbaik adalah memanggil join () di utas Anda ketika Anda mengharapkan mereka mati. Saya telah mengambil kebebasan dengan kode Anda untuk membuat loop berakhir (Anda dapat menambahkan kebutuhan pembersihan apa pun yang diperlukan di sana juga). Variabel dadu diperiksa kebenarannya pada setiap lintasan dan ketika Benar maka program keluar.

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
sumber
while Truekonyol, Anda harus joinsecara langsung - dan fungsi penggantian itu agak dipertanyakan. Mungkin def join(self, force=False): if force: self.die = Trueagar join()tidak berubah dengan join(force=True)membunuh mereka. Tetapi meskipun demikian, lebih baik memberi tahu kedua utas sebelum bergabung dengan salah satunya.
o11c
0

Versi perbaikan dari jawaban @Thomas K:

  • Mendefinisikan fungsi asisten is_any_thread_alive()menurut inti ini , yang dapat menghentikan main()secara otomatis.

Kode contoh:

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
sumber