Прерывание Python raw_input() в дочернем потоке с помощью ^C/KeyboardInterrupt

В многопоточной программе Python один поток иногда запрашивает консольный ввод с помощью встроенной функции raw_input(). . Я хотел бы иметь возможность закрыть программу в командной строке raw_input, набрав ^ C в оболочке (т. Е. С сигналом SIGINT). Однако, когда дочерний поток выполняет raw_input, ввод ^C ничего не делает — KeyboardInterrupt не вызывается до тех пор, пока я не нажму return (оставив raw_input).

Например, в следующей программе:

import threading

class T(threading.Thread):
    def run(self):
        x = raw_input()
        print x

if __name__ == '__main__':
    t = T()
    t.start()
    t.join()

Ввод ^C ничего не делает, пока ввод не будет завершен. Однако, если мы просто вызовем T().run() (т. е. однопоточный случай: просто запустим raw_input в основном потоке), ^C немедленно закроет программу.

Предположительно, это связано с тем, что SIGINT отправляется в основной поток, который приостанавливается (ожидает GIL), пока разветвленный поток блокирует чтение консоли. Основной поток не может выполнить свой обработчик сигнала, пока он не захватит GIL после возврата raw_input. (Пожалуйста, поправьте меня, если я ошибаюсь — я не эксперт по реализации многопоточности в Python.)

Есть ли способ читать из stdin в стиле raw_input, позволяя при этом обрабатывать SIGINT основным потоком и, таким образом, прерывать весь процесс?

[Я наблюдал описанное выше поведение в Mac OS X и нескольких разных Linux.]


Правка: я неправильно охарактеризовал основную проблему выше. При дальнейшем расследовании выяснилось, что основной поток вызывает join(), препятствующий обработке сигнала: сам Гвидо ван Россум объяснил, что основная блокировка получает в соединении непрерывно. Это означает, что сигнал на самом деле откладывается до тех пор, пока не завершится весь поток, так что на самом деле это вообще не имеет ничего общего с raw_input (просто тот факт, что фоновый поток блокируется, поэтому соединение не завершается).


person adrian    schedule 13.02.2012    source источник
comment
Нельзя просто совместить Потоки и Прерывания... [boromir.jpg]   -  person JBernardo    schedule 14.02.2012


Ответы (2)


Когда соединение вызывается без тайм-аута, оно не прерывается, но когда оно вызывается с тайм-аутом, его можно прервать. Попробуйте добавить произвольный тайм-аут и поместить его в цикл while:

while my_thread.isAlive():
    my_thread.join(5.0)
person amcnabb    schedule 20.03.2012
comment
Небольшое примечание: поток должен быть потоком daemon для завершения, чтобы работать с этим подходом. (Осторожно, тестируя это в интерактивном интерпретаторе: официальный основной поток — это сам интерпретатор, и этот поток не завершится в конце вашего main кода и, таким образом, не завершит даже daemon поток. -- если вы не сделаете что-то вроде sys.exit в конце вашего main, чтобы заставить интерпретатор завершить работу. Это не применяется, когда выполняется как скрипт) - person Irfy; 19.02.2019

На самом деле нет простого способа обойти это, и точка.

Один из подходов заключается в реорганизации и разбиении вашего кода таким образом, чтобы части функций, требующих прерывания Ctrl-C, выполнялись в основном потоке. Вы используете очереди для отправки запросов на выполнение, а также для значений результатов. Вам нужна одна входная очередь для основного потока и одна выходная очередь для неосновного потока; и скоординированный выход из основного потока. Очевидно, что таким образом в любой момент времени выполняется только одна блокирующая функция, что может быть не совсем тем, что вам нужно.

Вот рабочий пример этой идеи с несколько неправильным использованием семафоров для скоординированного выхода из основного потока.

person Irfy    schedule 14.02.2012