Подтвердить что ты не робот

Остановка встроенного 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;
}
4b9b3361

Ответ 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()