如何使用Python C/API封装多线程C++库?

0 投票
2 回答
2325 浏览
提问于 2025-04-16 14:19

这个问题有点长,但我希望能说得清楚。

我正在尝试用Python/C API来封装一个C++库。这个主要的库,假设叫做mylib,有自己的一套对象系统(就像是另一个语言的解释器),并通过Id来唯一标识它环境中的每个对象。它在init()函数中创建多个线程,并在不同的线程上做不同的事情(比如在一个线程上创建对象,在另一个线程上解释命令)。

现在我想分两层来封装它:

  1. 我创建了一个Dummy类,它的Id是mylib中的一个对象。Dummy的构造函数实际上调用了mylib中的一个函数来创建一个新对象并存储它的Id。Dummy类中的其他方法也类似地调用mylib中的相应函数。这部分不使用Python/C API。

  2. 我创建了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 个回答

0

谢谢你,Thomas,指出了这个误导性的信息。问题出在C++那边线程的初始化上。没错,这里不需要处理任何GIL(全局解释器锁),因为那些额外的C++线程并没有访问Python的C/API。

4

根据你的描述,这听起来根本不像是线程问题。你说你定义了 Dummy 类,但没有使用 Python 的 API,这就意味着 Dummy 的实例 不是 PyObjects,所以使用 reinterpret_cast 会出错。你不能仅仅通过实例化一个 C++ 类来创建 PyObjects;你需要遵循 Python 的对象系统,创建一个合适的 PyType 结构和一个 PyObject 结构,并正确初始化这两个结构。你还需要确保引用计数是正确的。

一旦这些问题解决了,关于线程你需要记住的主要一点是,任何涉及 Python 对象的调用,或者使用任何 Python API 的调用(除了获取 GIL 的函数) 都必须获取 GIL。如果你的 C++ 库中的任何线程尝试调用 Python 代码或访问 Python 对象,这些操作需要用 PyGILState_Ensure/PyGILState_Release 来包裹起来。

撰写回答