Прерывание 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, выполнялись в главном потоке. Вы используете очереди для отправки запросов на выполнение, а также для значений результатов. Вам нужна одна входная очередь для основного потока и одна выходная очередь для неосновного потока; и скоординированный выход из основного потока. Очевидно, что в данный момент времени выполняется только одна блокирующая функция, что может не соответствовать вашим ожиданиям.

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

Другие вопросы по тегам