Ctrl-C, т.е. KeyboardInterrupt, чтобы убить потоки в Python

Я где-то читал, что исключение 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?

Ответ 1

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

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

Выполняя это, пока вы показываете, вы в основном запускаете "нормальное" приложение без функций потока при запуске 1 потока и ждите, пока он не закончит работу.

Ответ 2

Если вы хотите, чтобы основной поток получал сигнал CTRL + C при подключении, это может быть сделано путем добавления тайм-аута к вызову join().

Кажется, что работает следующее (не забудьте добавить daemon=True, если вы хотите, чтобы на самом деле закончилось главное):

thread1.start()
while True:
    thread1.join(600)
    if not thread1.isAlive():
        break

Ответ 3

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

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

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