Остановка встроенного Python - PullRequest
       13

Остановка встроенного Python

23 голосов
/ 14 сентября 2009

Я встраиваю интерпретатор Python в программу на Си. Тем не менее, может случиться так, что при запуске некоторого скрипта Python через PyRun_SimpleString() произойдет бесконечный цикл или он будет выполняться слишком долго. Рассмотрим PyRun_SimpleString("while 1: pass"); Чтобы предотвратить блокирование основной программы, я думал, что смогу запустить интерпретатор в потоке.

Как мне прекратить выполнение скрипта python во встроенном интерпретаторе, работающем в потоке, без прерывания всего процесса?

Можно ли передать исключение переводчику? Должен ли я обернуть сценарий каким-либо другим сценарием, который будет прослушивать сигналы?

PS: я мог бы запустить питон в отдельном процессе, но это не то, что я хочу - если только это не последнее средство ...


Обновление:

Итак, теперь это работает. Спасибо Денис Откидач, еще раз!

Если я вижу это правильно, вы должны сделать две вещи: дать интерпретатору команду остановиться и return -1 в том же потоке, в котором работает PyRun_SimpleString ().

Для остановки у вас есть несколько возможностей: PyErr_SetString(PyExc_KeyboardInterrupt, "...") или PyErr_SetInterrupt() - при первом из них Python может выполнить еще несколько инструкций, а затем он останавливается, а при последующем немедленно выполнение прекращается.

Для return -1 вы используете Py_AddPendingCall() для добавления вызова функции в выполнение Python. В документах упоминается об этом начиная с версии 2.7 и 3.1, но он работает и на более ранних питонах (2.6 здесь). Начиная с версии 2.7 и 3.1 он также должен быть ориентирован на многопотоковое исполнение, то есть вы можете вызывать его, не получая GIL (?).

Чтобы можно было переписать приведенный ниже пример:

int quit() {
    PyErr_SetInterrupt();
    return -1;
}

Ответы [ 3 ]

11 голосов
/ 15 сентября 2009

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

1 голос
/ 14 сентября 2009

Ну, интерпретатор python должен быть запущен в отдельном потоке от программы встраивания, иначе у вас просто не будет возможности прервать интерпретатор.

Когда у вас это есть, возможно, вы можете использовать один из вызовов исключений Python API, чтобы вызвать исключение в интерпретаторе? См. Исключения Python C API

0 голосов
/ 15 мая 2019

Я хотел иметь возможность прервать любой активно работающий блок кода Python, в том числе отменить бесконечный цикл или просто какой-нибудь долго работающий код. В моем приложении однопоточный код Python представлен и оценен. Запрос на прерывание может прийти в любое время. Этот вопрос и ответ имели решающее значение, помогая мне на самом деле достичь этого.

Глядя на этот вопрос в 2019 году, решение здесь не работает для меня; Я пытался использовать Py_AddPendingCall(&quit, NULL); совершенно разными способами, но вызываемая функция никогда не выполнялась так, как ожидалось, или вообще не выполнялась. Это вызвало зависание движка Python и, в конечном итоге, сбой при отправке Py_FinalizeEx(). Я наконец нашел то, что мне нужно, в этом очень полезном ответе на аналогичный вопрос.

После запуска механизма Python я получаю идентификатор потока, используя пакет threading в коде Python. Я анализирую это в значение c long и сохраняю его.

Функция прерывания на самом деле довольно проста:
PyGILState_Ensure()
PyThreadState_SetAsyncExc(threadId, PyExc_Exception) с использованием идентификатора потока, который я сохранил ранее, и универсальный тип исключения
PyGILState_Release()

...