首页 > 解决方案 > 为什么在两个线程中释放和获取 GIL 会导致应用程序崩溃?

问题描述

我已经使用 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 之前锁定我的互斥锁吗?有没有可能导致死锁的情况?

标签: pythonpython-2.7python-multithreadingpython-c-apipython-extensions

解决方案


这个问题的根本原因是什么,我应该如何解决它?

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

如果我带上 PyEval_RestoreThread(_save); 行(在 catch 块中)到 catch 块的开头,不会发生崩溃。这是否意味着在 GIL 发布期间,我不应该调用任何 Python API?

如果将PyEval_RestoreThread(_save);行带到catch块的第一行,则表示catch块内的代码将由两个线程依次执行。因此,没有发生崩溃。

我应该在释放 GIL 之前锁定我的互斥锁吗?

我认为最好使用类似std::lock(...)或类似的结构同时将它们锁定在一起。但为此,您首先需要 GIL 的包装类,使其成为可锁定对象。

有没有可能导致死锁的情况?

如果按照建议将它们(GIL 释放和互斥锁)放在一起,我认为不会发生死锁。


推荐阅读