Почему сброс и получение 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 после этого? Есть ли случай, который может привести к тупику?


person Gupta    schedule 14.01.2019    source источник
comment
Если они уже находятся в потоке Python, я не думаю, что вам нужен дополнительный параллелизм в С++, хотя я могу ошибаться. Ваше первое редактирование звучит правильно. Я был бы счастливее, если бы вы использовали объект класса (RAII), что я и сделаю, если у меня будет время исследовать и написать ответ.   -  person Kenny Ostrom    schedule 16.01.2019


Ответы (1)


Какова основная причина этой проблемы и как ее исправить?

Основная причина проблемы заключается в том, что если вы запустите DoComputation1() в двух потоках, и этот метод выдаст исключение, оба потока будут запускать блок catch. В блоке catch использовались некоторые функции Python API. Таким образом, это означает, что внутри реализации Python существуют два потока, которые приведут к сбою.

Если я принесу PyEval_RestoreThread(_save); строку (в блоке catch) до начала блока catch, сбоев не происходит. Означает ли это, что во время выпуска GIL я не должен вызывать какой-либо Python API?

Если вывести PyEval_RestoreThread(_save);line на первую строку блока catch, это означает, что код внутри блока catch будет выполняться двумя потоками последовательно. Так что никакого сбоя не происходит.

Должен ли я заблокировать свой мьютекс перед тем, как освободить GIL после этого?

Я думаю, что лучше связать их вместе, используя структуру типа std::lock(...) или что-то в этом роде. Но для этого вам сначала понадобится класс-оболочка для GIL, чтобы сделать его блокируемым. объект.

Есть ли случай, который может привести к тупику?

Если оба из них (освобождение GIL и блокировка мьютекса) взяты вместе, как было предложено, я не думаю, что может возникнуть взаимоблокировка.

person Gupta    schedule 09.04.2019