调用带有线程的Py_Finalize时出现断言错误(仅限于3.X)

3

当我从不同的C线程调用C-API的Py_Finalize()时,我会得到一个错误输出。

我看到的错误是:

Exception ignored in: <module 'threading' from 'C:\\Python34-32\\Lib\\threading.py'>
Traceback (most recent call last):
  File "C:\Python34-32\Lib\threading.py", line 1289, in _shutdown
    assert tlock.locked()
AssertionError:

只有在Python 3.X(测试过3.4.2)中才会出现这种情况,而在Python 2.7中完全相同的代码没有任何问题。
下面是一个最简示例,它展示了在使用C线程时出现该问题,但当所有操作都在单个C线程上执行时则不会出现:
#include <iostream>
#include <fstream>
#include <thread>
#include <cassert>

#include <Python.h>

void make_file()
{
   std::fstream file("my_test.py", std::ios::out);
   file << 
      "import threading\n"   << 
      "def my_function():\n" << 
      "    pass\n"             ;
   file.close();
}

void exec()
{
   PyGILState_STATE gstate = PyGILState_Ensure();
   PyObject* pdict = PyDict_New();
   PyDict_SetItemString(pdict, "__builtins__", PyEval_GetBuiltins());

   PyRun_String("import my_test", Py_file_input, pdict, pdict);
   PyRun_String("my_test.my_function()", Py_file_input, pdict, pdict);
   assert(!PyErr_Occurred());
   PyGILState_Release(gstate);
}

void basic()
{
   std::cout << "--Starting Basic--" << std::endl;

   Py_Initialize();
   PyEval_InitThreads();
   PyThreadState* threadState = PyEval_SaveThread();

   exec();

   PyEval_RestoreThread(threadState);
   Py_Finalize();

   std::cout << "--Basic Complete--" << std::endl;
}

void with_thread()
{
   std::cout << "--Starting With Thread--" << std::endl;

   Py_Initialize();
   PyEval_InitThreads();
   PyThreadState* threadState = PyEval_SaveThread();

   std::thread t(exec);
   t.join();

   PyEval_RestoreThread(threadState);
   Py_Finalize();

   std::cout << "--With Thread Complete--" << std::endl;
}

int main(int argc, char* argv[])
{
   make_file();
   basic();
   with_thread();

   return 0;
}

输出

--Starting Basic--
--Basic Complete--
--Starting With Thread--
Exception ignored in: <module 'threading' from 'C:\\Python34-32\\Lib\\threading.py'>
Traceback (most recent call last):
  File "C:\Python34-32\Lib\threading.py", line 1289, in _shutdown
    assert tlock.locked()
AssertionError:
--With Thread Complete--

主函数中basic()/with_thread()的顺序并不重要,我甚至可以多次包含这些行而没有影响,每个with_thread()调用都会导致错误输出。
编辑:
将threadState更改为全局变量,然后将exec更改为:
void exec()
{
   //PyGILState_STATE gstate = PyGILState_Ensure();
   PyEval_RestoreThread(threadState); 
   PyObject* pdict = PyDict_New();
   PyDict_SetItemString(pdict, "__builtins__", PyEval_GetBuiltins());

   PyRun_String("import my_test", Py_file_input, pdict, pdict);
   PyRun_String("my_test.my_function()", Py_file_input, pdict, pdict);
   assert(!PyErr_Occurred());
   //PyGILState_Release(gstate);
   threadState = PyEval_SaveThread();
}

如果我不抓取全局解释器锁(GIL),这个错误就会消失,但我需要在我的库的用户之间协调一个全局值(在我的实际代码中,exec()函数可以由任何人编写,我还有更多的初始化工作要做)。您有没有关于如何使GIL抓取更像原始示例一样更隔离但仍保持线程兼容性的见解?


你有没有得到更多关于这个的信息? - AJ Venturella
不,我最终做的基本上就是我在底部所做的编辑展示的内容。唯一的区别是我实际上没有使用全局变量,而是创建了一个包含它的类,任何使用Python线程的用户都需要来回传递它。 - teeks99
1个回答

7
尝试添加以下内容:
Py_DECREF(PyImport_ImportModule("threading"));

之后

PyEval_InitThreads();

1
在上述示例中,当在单线程或多线程示例中使用时,似乎效果不错。为什么?!? - teeks99
我曾经遇到过同样的问题,这个方法解决了它。有人知道为什么这个方法有效吗? - jackscorrow
1
对于pybind11:pybind11::scoped_interpreter interpreter; (void)pybind11::detail::get_internals(); Py_DECREF(PyImport_ImportModule("threading")); - detail::get_internals() 调用执行 PyEval_InitThreads()。 - mheyman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接