Ctrl-C, т.е. KeyboardInterrupt, чтобы убить потоки в Python - PullRequest
22 голосов
/ 09 ноября 2010

Я где-то читал, что KeyboardInterrupt исключение возникает только в основном потоке в Python. Я также читал, что основной поток блокируется, пока выполняется дочерний поток. Значит ли это, что CTRL + C никогда не сможет достичь дочернего потока. Я попробовал следующий код:

def main():
    try:
        thread = threading.Thread(target=f)
        thread.start()  # thread is totally blocking (e.g., while True)
        thread.join()
    except KeyboardInterrupt:
        print "Ctrl+C pressed..."
        sys.exit(1)

def f():
    while True:
        pass  # do the actual work

В этом случае CTRL + C не влияет на выполнение. Как будто он не способен слушать сигнал. Я неправильно это понимаю? Есть ли другой способ убить поток, используя CTRL + C ?

Ответы [ 3 ]

13 голосов
/ 14 января 2012

Если вы хотите, чтобы основной поток получал сигнал CTRL + C при присоединении, это можно сделать, добавив таймаут к вызову join().* Кажется, работает следующее (не забудьте добавить daemon=True, если вы хотите, чтобы main фактически заканчивался):

thread1.start()
while True:
    thread1.join(600)
    if not thread1.isAlive():
        break
10 голосов
/ 20 января 2011

Проблема в том, что вы используете thread1.join(), что заставит вашу программу ждать, пока этот поток не завершится.

Сигналы всегда будут перехватываться основным процессом, потому что он принимает сигналы, а процесс имеет потоки.

Делая это так, как вы показываете, вы в основном запускаете «обычное» приложение без потоковых функций, когда вы запускаете 1 поток и ждете, пока оно не завершится.

2 голосов
/ 19 февраля 2019

В Python верно, что KeyboardInterrupt исключения генерируются только в главном потоке каждого процесса. Но, как упоминалось в других ответах, также верно, что метод Thread.join блокирует вызывающий поток, , включая KeyboardInterrupt исключения . Вот почему Ctrl + C , похоже, не имеет никакого эффекта: выполнение в главном потоке остается заблокированным в строке thread.join().

Таким образом, простое решение вашего вопроса состоит в том, чтобы сначала добавить аргумент времени ожидания к thread.join() и поместить этот вызов в цикл, который заканчивается при выходе из дочернего потока, чтобы исключения KeyboardInterrupt могли подниматься после каждого тайм-аута, а во-вторых, сделать дочерний поток daemonic , что означает, что его родитель (основной поток здесь) будет уничтожать его при выходе (только потоки, не являющиеся демонами, не уничтожаются, а присоединяются, когда их родительский выход):

def main():
    try:
        thread = threading.Thread(target=f, daemon=True)  # create a daemon child thread
        thread.start()

        while thread.is_alive():
            thread.join(1)  # join shortly to not block KeyboardInterrupt exceptions
    except KeyboardInterrupt:
        print "Ctrl+C pressed..."
        sys.exit(1)

def f():
    while True:
        pass  # do the actual work

Но лучшим решением, если вы управляете кодом дочернего потока, является информирование дочернего потока о том, что он должен завершиться изящно (а не резко, как при первом решении), например, с помощью threading.Event:

def main():
    try:
        event = threading.Event()
        thread = threading.Thread(target=f, args=(event,))
        thread.start()
        event.wait()  # wait forever but without blocking KeyboardInterrupt exceptions
    except KeyboardInterrupt:
        print "Ctrl+C pressed..."
        event.set()  # inform the child thread that it should exit
        sys.exit(1)

def f(event):
    while not event.is_set():
        pass  # do the actual work
...