为什么在两个线程中释放和获取 GIL 会导致应用程序崩溃?

Why relasing and acquiring GIL within two threads causes an application crash?

我使用 C++ 开发了一个 Python 扩展。这个模块的唯一功能是这样的:

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 中(在块的开头锁定并在块的末尾解锁)也不能解决问题。这个问题的根本原因是什么,我该如何解决?

我正在使用 Visual studio 2013 和 Python 2.7 在 Windows 10 上开发。

编辑 1:
如果我将 PyEval_RestoreThread(_save); 行(在 catch 块中)带到 catch 块的开头,则不会发生崩溃。是不是说在GIL发布的这段时间里,我不应该调用任何Python API?

编辑 2:
我还需要使用互斥锁保护我的 API1 方法免受并发线程的影响。我应该在释放之后的 GIL 之前锁定我的互斥量吗?有没有可能导致死锁的case?

What is the root reason of this problem and how should I fix it?

问题的根本原因是如果你在两个线程中运行 DoComputation1() 并且这个方法抛出异常,两个线程都会运行 catch 块。在 catch 块中,使用了一些 Python API 函数。因此,这意味着 Python 实现中确实存在两个线程,这将导致崩溃。

If I bring the PyEval_RestoreThread(_save); line (in the catch block) to the beginning of the catch block, no crash happends. Does it mean that during the time that GIL is released, I should not call any Python API?

如果把PyEval_RestoreThread(_save);行放到catch块的第一行,就意味着catch块里面的代码会被两个线程依次执行。所以,没有崩溃发生。

Should I lock my mutex before releaseing the GIL of after that?

我认为最好使用 std::lock(...) 之类的构造函数将它们同时锁定在一起。但是为此,您首先需要一个 GIL 的包装器 class 以使其成为一个 lockable 对象。

Is there any case that may lead to a deadlock?

如果按照建议将两者(GIL 释放和互斥锁)结合起来,我认为不会发生死锁。