如何使用Python C/API封装多线程C++库?
这个问题有点长,但我希望能说得清楚。
我正在尝试用Python/C API来封装一个C++库。这个主要的库,假设叫做mylib,有自己的一套对象系统(就像是另一个语言的解释器),并通过Id来唯一标识它环境中的每个对象。它在init()
函数中创建多个线程,并在不同的线程上做不同的事情(比如在一个线程上创建对象,在另一个线程上解释命令)。
现在我想分两层来封装它:
我创建了一个Dummy类,它的Id是mylib中的一个对象。Dummy的构造函数实际上调用了mylib中的一个函数来创建一个新对象并存储它的Id。Dummy类中的其他方法也类似地调用mylib中的相应函数。这部分不使用Python/C API。
我创建了
mylibmodule.cpp
,它使用Python/C API来提供将从Python解释器中调用的函数。我在
PyMODINIT_FUNC init_mylib()
中调用了mylib的init()
函数。我编写了类似这样的函数:
static PyObject * py_new_Dummy(PyObject* self, PyObject *args){ // ... process arguments return reinterpret_cast<PyObject*>(new Dummy); }
需要注意的是,Dummy的构造函数确实调用了在使用pthreads创建的线程中执行的mylib函数。
我把它编译成_mylib.so,并且有一个mylib.py:
import _mylib
class MyClass(obj):
def __init__(self, *args)
self.__ptr = _mylib.py_new_Dummy()
现在说说实际的问题:我可以在Python解释器中导入mylib,但一旦我尝试:
a = MyClass(some_args)
我就会遇到段错误。gdb的回溯显示:
程序接收到信号SIGSEGV,段错误。
__pthread_mutex_lock (mutex=0x0) 在pthread_mutex_lock.c:50
更有趣的是,如果我在mylib代码中禁用多线程的创建(仍然链接了pthreads),我可以创建MyClass实例,但在退出Python解释器时会出现段错误。
Python文档中的“薄冰”部分(http://docs.python.org/extending/)没有给我带来启发。我在想是否应该在mylibmodule.cpp中的所有Python C/API调用周围使用PyGILState_Ensure和PyGILState_Release,还是应该使用Py_BEGIN_ALLOW_THREADS和Py_END_ALLOW_THREADS?
有没有人能帮忙?有没有关于Python如何与pthreads配合使用的权威文档?
2 个回答
谢谢你,Thomas,指出了这个误导性的信息。问题出在C++那边线程的初始化上。没错,这里不需要处理任何GIL(全局解释器锁),因为那些额外的C++线程并没有访问Python的C/API。
根据你的描述,这听起来根本不像是线程问题。你说你定义了 Dummy
类,但没有使用 Python 的 API,这就意味着 Dummy
的实例 不是 PyObjects,所以使用 reinterpret_cast 会出错。你不能仅仅通过实例化一个 C++ 类来创建 PyObjects;你需要遵循 Python 的对象系统,创建一个合适的 PyType 结构和一个 PyObject 结构,并正确初始化这两个结构。你还需要确保引用计数是正确的。
一旦这些问题解决了,关于线程你需要记住的主要一点是,任何涉及 Python 对象的调用,或者使用任何 Python API 的调用(除了获取 GIL 的函数) 都必须获取 GIL。如果你的 C++ 库中的任何线程尝试调用 Python 代码或访问 Python 对象,这些操作需要用 PyGILState_Ensure
/PyGILState_Release
来包裹起来。