Остановка встроенного Python
Я внедряю интерпретатор Python в программу C. Однако может случиться так, что при запуске некоторого python script через PyRun_SimpleString()
будет работать в бесконечном цикле или выполнить слишком долго. Рассмотрим PyRun_SimpleString("while 1: pass");
Чтобы предотвратить блокировку основной программы, я думал, что могу запустить интерпретатор в потоке.
Как остановить выполнение python script во встроенном интерпретаторе, запущенном в потоке, не убивая весь процесс?
Можно ли передать исключение интерпретатору? Должен ли я обернуть script под какой-нибудь другой script, который будет слушать сигналы?
PS: я мог запускать python в отдельном процессе, но это не то, что я хочу - если это не последнее средство...
Update:
Итак, теперь это работает. Спасибо Денису Откидачу, еще раз!
Если я вижу это правильно, вам нужно сделать две вещи: попросите интерпретатора остановиться и return -1
в том же потоке, что и ваш PyRun_SimpleString().
Чтобы остановиться, у вас есть несколько возможностей: PyErr_SetString(PyExc_KeyboardInterrupt, "...")
или PyErr_SetInterrupt()
- первый из них может оставить Python еще несколько инструкций, а затем он остановится, а позже он немедленно прекратит выполнение.
В return -1
вы используете Py_AddPendingCall()
для ввода вызова функции в выполнение Python. Документы упоминают его с версии 2.7 и 3.1, но он работает и с более ранними Pythons (здесь 2.6). Начиная с версии 2.7 и 3.1 он также должен быть потокобезопасным, то есть вы можете назвать его, не приобретая GIL (?).
Итак, можно переписать следующий пример:
int quit() {
PyErr_SetInterrupt();
return -1;
}
Ответы
Ответ 1
Вы можете использовать Py_AddPendingCall()
, чтобы добавить исключение функции, которое вызывается в следующий интервал проверки (подробнее см. в документах sys.setcheckinterval()
). Вот пример с вызовом Py_Exit()
(который работает для меня, но, вероятно, это не то, что вам нужно), замените его на Py_Finalize()
или один из PyErr_Set*()
:
int quit(void *) {
Py_Exit(0);
}
PyGILState_STATE state = PyGILState_Ensure();
Py_AddPendingCall(&quit, NULL);
PyGILState_Release(state);
Этого должно быть достаточно для любого кода с чистым питоном. Но обратите внимание, что некоторые функции C могут выполняться некоторое время как одна операция (был пример с длинным запуском поиска в регулярном выражении, но я не уверен, что это все еще актуально).
Ответ 2
Ну, интерпретатор python должен быть запущен в отдельном потоке из программы внедрения, иначе вы просто не сможете прервать интерпретатор.
Если у вас это есть, возможно, вы можете использовать один из вызовов исключения API Python для запуска исключения в интерпретаторе? См. Исключения API Python C
Ответ 3
Я хотел иметь возможность прервать любой активно работающий блок кода Python, в том числе отменить бесконечный цикл или просто какой-нибудь долго работающий код. В моем приложении однопоточный код Python представлен и оценен. Запрос на прерывание может прийти в любое время. Этот вопрос и ответ имели решающее значение, помогая мне на самом деле достичь этого.
Глядя на этот вопрос в 2019 году, решение здесь не работает для меня; Я пытался использовать Py_AddPendingCall(&quit, NULL);
разными способами, но вызываемая функция никогда не выполняется должным образом или вообще не выполняется. Это вызвало зависание движка Python и, в конечном итоге, сбой при Py_FinalizeEx()
. Я наконец нашел то, что мне нужно, в этом очень полезном ответе на похожий вопрос.
После запуска движка Python я получаю идентификатор потока, используя пакет threading
в коде Python. Я анализирую это в ac long value и сохраняю его.
Функция прерывания на самом деле довольно проста:
PyGILState_Ensure()
PyThreadState_SetAsyncExc(threadId, PyExc_Exception)
с использованием идентификатора потока, который я сохраняю ранее, и универсальный тип исключения
PyGILState_Release()