【问题标题】:Embedded Python Segfaults嵌入式 Python 段错误
【发布时间】:2012-02-11 20:57:20
【问题描述】:

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

BT 会贴在底部。

一些背景:

  1. 我的应用程序创建了许多派生 C++ 类的多个实例,并运行基类的 Run() 函数,该函数使用虚拟方法来确定要执行的操作。
  2. 一个派生类使用 Python 类,Grasp_Behavior(class) in grasp_behavior(module)
  3. 经过大量阅读,我已经使用 Python API 实现了(2)(摘录如下)
  4. 我生成了该类的 2 个实例,并以“并行”方式运行它们(python interpr 并不真正并行运行)
  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

【问题讨论】:

    标签: c++ python multithreading segmentation-fault embedding


    【解决方案1】:

    首先,在 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 不同。

    此外,您应该在主线程启动时(在其他线程启动之前)初始化一次解释器,并在关闭除主线程之外的所有线程后完成。

    【讨论】:

    • 嘿牦牛,非常感谢您的回复!我只是想清理一些东西并获得您的输入:1)请注意,我只在调用 Py_InitThreads 时才释放一次锁。我读到激活锁。我找不到任何以您的方式释放隐式锁的具体示例,您能否为我的应用程序重新表述它(接下来描述)。 (2) 我的类在主线程中实例化。他们的 run 方法在 boost 线程中被调用(使用 boost::bind)。运行线程调用 Py_Ensure 和 Py_ReleaseGIL。然后退出到实例化更多类的主线程。
    • PyEval_InitThreads 创建(并获取)GIL。它应该从主线程调用。完成所有设置 Py* 调用后,您应该通过调用 PyEval_SaveThread 释放 GIL(如果您没有在退出时进行清理,请忽略返回值)。如果需要,您可以在此之前启动线程,PyGILState_Ensure 方法中的 Run 调用将阻塞,直到主线程释放 GIL。发生这种情况时,其中一个被阻塞的线程将继续(并且该线程现在将拥有 GIL),直到它调用 PyGILState_Release,此时另一个线程可能会进入。
    • 当然,此时主线程没有什么不同,在调用任何 Python API 之前也必须使用PyGILState_Ensure。请记住,这并不意味着不会有两个Run 方法同时运行。如果您开始在其中一个线程中执行 Python 代码,解释器将反复释放并重新获取 GIL,让其他线程运行。像 file.read() 这样的 Python IO 调用也会释放 GIL。为获得最佳结果,切勿将获取的 GIL 保留超过需要的时间。
    • 是的,将PyEval_ReleaseLock 替换为PyEval_SaveThread。我没有意识到这是在你的“类”的构造函数中,所以它会运行多次。这意味着您必须在调用PyImport_ImportModule("grasp_behavior") 之前调用PyGILState_Ensure(完成后调用PyGILState_Release)。对于第一次调用,它将初始化 Python,创建 GIL(InitThreads),释放它(SaveThread),重新获取它(Ensure),做一些事情(ImportModule 等)并在此时再次释放它(Release) Run 将免费使用PyGILState_Ensure 获取它。
    • 对于未来的调用,它将从获取 GIL (PyGILState_Ensure) 开始。事实上,我会将初始化代码(if-block)移动到main 或在整个程序启动时运行的其他一些函数。
    【解决方案2】:

    Boost 是否提供了与pthread_once() 函数的道德等效项,该函数允许某些初始化任务仅运行一次,而不管有多少线程尝试同时运行它?如果这是我要调试的问题,我会尝试保护 PyImport_ImportModule 免受多线程中的多次调用,并且为此使用标准工具将是我的第一次尝试。

    【讨论】:

    • 好主意,明天我会在课堂上添加一个静态标志。
    猜你喜欢
    • 2013-01-28
    • 1970-01-01
    • 2011-01-08
    • 2018-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多