Остановка встроенного Python
-
07-07-2019 - |
Вопрос
Я встраиваю интерпретатор Python в программу на языке Си.Однако может случиться так, что при запуске какого-либо скрипта python через PyRun_SimpleString()
запустится в бесконечный цикл или будет выполняться слишком долго.Рассмотреть PyRun_SimpleString("while 1: pass");
Предотвращая блокировку основной программы, я подумал, что смогу запустить интерпретатор в потоке.
Как мне остановить выполнение скрипта python во встроенном интерпретаторе, запущенном в потоке, не останавливая весь процесс?
Можно ли передать исключение интерпретатору?Должен ли я обернуть скрипт под какой-нибудь другой скрипт, который бы прослушивал сигналы?
PS:Я мог бы запустить python в отдельном процессе, но это не то, чего я хочу - если только это не последнее средство...
Обновить:
Итак, теперь это работает.Еще раз спасибо вам, Денис Откидач!
Если я правильно понимаю, вам нужно сделать две вещи:скажите переводчику, чтобы он остановился и 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;
}
Решение
Вы можете использовать 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);
Этого должно быть достаточно для любого кода на чистом python.Но обратите внимание, что некоторые функции C могут некоторое время выполняться как одна операция (был пример с длительным поиском по регулярному выражению, но я не уверен, что это все еще актуально).
Другие советы
Что ж, интерпретатор python должен быть запущен в отдельном потоке от программы встраивания, иначе у вас просто никогда не будет возможности прервать работу интерпретатора.
Когда у вас это будет, возможно, вы сможете использовать один из вызовов исключений Python API для запуска исключения в интерпретаторе?Видишь Исключения из API Python C
Я хотел иметь возможность прерывать любой активно запущенный блок кода Python, включая отмену бесконечного цикла или просто какого-то долго выполняющегося кода.В моем приложении отправляется и оценивается однопоточный код Python.Запрос на прерывание может поступить в любое время.Этот вопрос и ответ на него сыграли решающую роль в том, чтобы помочь мне действительно достичь этого.
Рассматривая этот вопрос в 2019 году, решение здесь у меня не сработало;Я пытался использовать Py_AddPendingCall(&quit, NULL);
довольно несколькими различными способами, но вызванная функция никогда не выполнялась должным образом или вообще не выполнялась.Это привело к зависанию движка Python и, в конечном итоге, к сбою при отправке Py_FinalizeEx()
.Наконец-то я нашел то, что мне было нужно, в этом очень полезном ответ на аналогичный вопрос.
После того, как я запускаю движок Python, я извлекаю идентификатор потока, используя threading
пакет в коде Python.Я преобразую это в длинное значение c и сохраняю его.
Функция прерывания на самом деле довольно проста:
PyGILState_Ensure()
PyThreadState_SetAsyncExc(threadId, PyExc_Exception)
используя идентификатор потока, который я сохранил ранее, и общий тип исключения
PyGILState_Release()