嵌入式Python崩溃

4 投票
2 回答
3091 浏览
提问于 2025-04-17 10:24

我的多线程应用在调用 PyImport_ImportModule("my_module") 时出现了段错误。

下面会贴出错误回溯信息。

一些背景信息:

  1. 我的应用创建了多个派生自C++的类的实例,并运行基类的 Run() 函数,这个函数使用虚方法来决定要做什么。
  2. 其中一个派生类使用了一个Python类,叫做 Grasp_Behavior,它在 grasp_behavior 模块里。
  3. 经过大量阅读,我使用Python API来实现了第二点(下面有相关代码片段)。
  4. 我生成了两个这个类的实例,并“并行”运行它们(其实Python解释器并不是真正的并行运行)。
  5. 我尝试生成另一个这个类的实例时,在 PyImport_ImportModule 处出现了段错误。

我在想,也许我不能在同一个解释器中导入同一个模块两次。但我不知道怎么检查这个。我想我需要看看 grasp_behavior 是否在某个字典里,但我不知道是哪个字典,也许是 __main__ 模块的字典?

不过我可能错了,任何建议都将非常有帮助!

在构造函数中:

//Check if Python is Initialized, otherwise initialize it
if(!Py_IsInitialized())
{
    std::cout << "[GraspBehavior] InitPython: Initializing the Python Interpreter" << std::endl;
    Py_Initialize();
    PyEval_InitThreads(); //Initialize Python thread ability
    PyEval_ReleaseLock(); //Release the implicit lock on the Python GIL
}

// --- Handle Imports ----

PyObject * pModule = PyImport_ImportModule("grasp_behavior");
if(pModule == NULL)
{
    std::cout << "[GraspBehavior] InitPython: Unable to import grasp_behavior module: ";
    PyErr_Print();
}
 // --- Get our Class Pointer From the Module ...
PyObject * pClass = PyObject_GetAttrString(pModule, "Grasp_Behavior");
if(pClass == NULL)
{
    std::cout << "[GraspBehavior] InitPython: Unable to get Class from Module: ";
    PyErr_Print();
}
Py_DECREF(pModule); //clean up, this is a new reference

behavior_instance_ = PyObject_Call(pClass, pArguments_Tuple, pArguments_Dict);
if(behavior_instance_ == NULL)
{
    std::cout << "[GraspBehavior] InitPython: Couldn't generate instance: ";
    PyErr_Print();
}
Py_DECREF(pArguments_Tuple);
Py_DECREF(pArguments_Dict);
Py_DECREF(pClass);

在这里,请注意我只有在Python解释器没有初始化的情况下才会初始化它。我认为它会为整个进程初始化。

Run() 方法中(从boost线程运行):

std::cout << "[GraspBehavior] PerformBehavior: Acquiring Python GIL Lock ..." << std::endl;
PyGILState_STATE py_gilstate;
py_gilstate = PyGILState_Ensure();

/* ---- Perform Behavior Below ----- */

std::vector<std::pair<double, double> > desired_body_offsets;
//desired_body_offsets.push_back( std::pair<double, double>(0.6, 0));
PyObject * base_positions = GetTrialBasePositions(my_env_, desired_body_offsets);

PyObject * grasps = EvaluateBasePositions(my_env_, base_positions);

//Did we get any grasps? What do we do with them? [TODO]
if(grasps != NULL)
{
    std::cout << grasps->ob_type->tp_name << std::endl;
    std::cout << "Number of grasps: " << PyList_Size(grasps) << std::endl;
    successful_ = true;
}

/* --------------------------------- */

std::cout << "[GraspBehavior] PerformBehavior: Releasing Python GIL Lock ..." << std::endl;
PyGILState_Release(py_gilstate);

在这里,我使用了 PyGILState 锁。我阅读了一段时间,发现很多人使用的锁的方式似乎是Python的旧风格……也许我需要切换到其他方式。


错误回溯信息:

Program received signal SIGSEGV, Segmentation fault.
0x00007fffee9c4330 in ?? () from /usr/lib/libpython2.6.so.1.0
(gdb) bt
#0  0x00007fffee9c4330 in ?? () from /usr/lib/libpython2.6.so.1.0
#1  0x00007fffee99ff09 in PyEval_GetGlobals ()
   from /usr/lib/libpython2.6.so.1.0
#2  0x00007fffee9bd993 in PyImport_Import () from /usr/lib/libpython2.6.so.1.0
#3  0x00007fffee9bdbec in PyImport_ImportModule ()
   from /usr/lib/libpython2.6.so.1.0
#4  0x000000000042d6f0 in GraspBehavior::InitPython (this=0x7948690)
    at grasp_behavior.cpp:241

2 个回答

1

Boost有没有类似于pthread_once()这个函数的功能?这个函数的作用是确保某个初始化任务只执行一次,不管有多少线程同时尝试去执行它。如果这是我需要解决的问题,我会尝试保护PyImport_ImportModule这个函数,防止它在多个线程中被多次调用,使用一个标准的工具来实现这一点会是我首先考虑的办法。

5

首先,当全局解释器锁(GIL)被释放时,你不能调用任何Python的API函数(除了获取GIL的调用)。

下面这段代码会崩溃:

PyEval_ReleaseLock();

PyObject * pModule = PyImport_ImportModule("grasp_behavior");

在你完成设置后释放GIL,然后在需要的时候再重新获取(在你的 Run() 函数中)。

另外,PyEval_ReleaseLock 已经不推荐使用了,这种情况下你应该用 PyEval_SaveThread

PyThreadState* tstate = PyEval_SaveThread();

这个函数会保存线程的状态并释放GIL。

然后,在你开始结束解释器之前,做这个:

PyEval_RestoreThread(tstate);

传递 PyEval_SaveThread 调用的返回值。

Run() 函数中,你应该像现在这样使用 PyGILState_EnsurePyGILState_Release,但你需要考虑C++的异常情况。现在如果 Run() 抛出异常,PyGILState_Release 就不会被调用。

PyGILState 调用的一个好处是,无论GIL是否被获取,你都可以使用它们,它们会自动处理好,而不像以前的API那样。

此外,你应该在启动时在主线程中初始化解释器(在其他线程启动之前),并在关闭所有线程但主线程后进行最终清理。

撰写回答