Почему получение и получение GIL в двух потоках вызывает сбой приложения?
Я разработал расширение Python с использованием C++. Единственная функция этого модуля примерно такая:
static PyObject *TestModule_API1(PyObject *self, PyObject *args)
{
PyThreadState *_save;
_save = PyEval_SaveThread();
try
{
DoComputation1();
PyEval_RestoreThread(_save);
}
catch (const std::exception & e)
{
PyObject * py_exception = PyErr_NewException((char*)"pyhms.error", nullptr, nullptr);
PyErr_SetString(py_exception, e.what());
PyEval_RestoreThread(_save);
return nullptr;
}
Py_RETURN_NONE;
}
Всякий раз, когда я вызываю этот метод с двумя потоками Python, если DoComputation1()
метод выдает исключение, приложение вылетает. Даже помещение всего блока try в std::mutex (блокировка в начале и разблокировка в конце блока) не решает проблему. В чем корень этой проблемы и как мне ее исправить?
Я занимаюсь разработкой на Windows 10 с использованием Visual Studio 2013 и Python 2.7.
Изменить 1:
Если я принесу PyEval_RestoreThread(_save);
линия (в блоке catch) к началу блока catch, аварийного завершения не происходит. Означает ли это, что во время выпуска GIL я не должен вызывать какой-либо Python API?
Изменить 2:
Мне также нужно защитить мой метод API1 от одновременных потоков с использованием мьютекса. Должен ли я заблокировать свой мьютекс перед выпуском GIL после этого? Есть ли случай, который может привести к тупику?
0 ответов
В чем корень этой проблемы и как мне ее исправить?
Основная причина проблемы заключается в том, что если вы запустите DoComputation1()
в двух потоках, и этот метод вызывает исключение, оба потока будут запускать блок catch. В блоке catch были использованы некоторые функции Python API. Таким образом, это означает, что внутри реализации Python существуют два потока, которые приведут к сбою.
Если я принесу PyEval_RestoreThread(_save); линия (в блоке catch) к началу блока catch, аварийного завершения не происходит. Означает ли это, что во время выпуска GIL я не должен вызывать какой-либо Python API?
Если вы принесете PyEval_RestoreThread(_save);
строка в первую строку блока catch означает, что код внутри блока catch будет выполняться двумя потоками последовательно. Так что сбоев не происходит.
Должен ли я заблокировать свой мьютекс перед выпуском GIL после этого?
Я думаю, что лучше заблокировать их вместе, используя такую конструкцию, как std::lock(...)
или что-то типа того. Но для этого вам сначала понадобится класс-оболочка для GIL, чтобы сделать его блокируемым объектом.
Есть ли случай, который может привести к тупику?
Если оба из них (освобождение GIL и блокировка мьютекса) взяты вместе, как предложено, я не думаю, что может произойти тупик.