Прерывание 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
вообще (только тот факт, что фоновый поток блокируется, так что соединение не завершается).
2 ответа
Когда соединение вызывается без тайм-аута, оно является непрерывным, но когда оно вызывается с тайм-аутом, оно прерывается. Попробуйте добавить произвольное время ожидания и поместить его в цикл while:
while my_thread.isAlive():
my_thread.join(5.0)
Это действительно нелегкий путь, точка.
Один из подходов состоит в том, чтобы реорганизовать и разбить ваш код таким образом, чтобы части функций, которым требуется прерываемость Ctrl-C, выполнялись в главном потоке. Вы используете очереди для отправки запросов на выполнение, а также для значений результатов. Вам нужна одна входная очередь для основного потока и одна выходная очередь для неосновного потока; и скоординированный выход из основного потока. Очевидно, что в данный момент времени выполняется только одна блокирующая функция, что может не соответствовать вашим ожиданиям.
Вот рабочий пример этой идеи со слегка извращенным использованием семафоров для скоординированного выхода из основного потока.