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

Другие вопросы по тегам