嵌入式 Python 段错误

发布于 2024-12-27 11:45:12 字数 3625 浏览 1 评论 0原文

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

BT 将发布在底部。

一些背景:

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

My multi-threaded app segfaults on a call to PyImport_ImportModule("my_module").

The BT will be posted at the bottom.

Some background:

  1. My app creates multiple instances of many derived C++ classes and runs the base class's Run() function which uses a virtual method to determine what to do.
  2. One derived class uses a Python Class, Grasp_Behavior(class) in grasp_behavior(module)
  3. After extensive reading I have used the Python API to achieve (2) (exerpts below)
  4. I generate 2 instances of said class, and run them in "parallel" (python interpr doesn't really run parallel)
  5. I attempt to generate another instance of said class, segfault at PyImport_ImportModule

My thoughts are that perhaps I cannot import a module twice in the same interpreter. But I can't figure out how to check it. I assume I need to see if grasp_behavior is in a dictionary but I don't know which one, perhaps I get __main__ module's dictionary?

But I might be wrong, any advice would be incredibly helpful!

In the constructor:

//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);

Here, note that I only initialize the Python interpreter if it has not been initialized. I assume it gets initialized for the entire process.

In the Run() method (ran from a boost thread):

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);

Here, I have gone with PyGILState lock. I read for a while and it seemed the some of the articles that many people are link use an older style of locking in Python ... perhaps I may have to switch this.


Backtrace:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

枉心 2025-01-03 11:45:12

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

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

First of all, you must not call any Python API functions when the GIL is released (except the GIL acquiring calls).

This code will crash:

PyEval_ReleaseLock();

PyObject * pModule = PyImport_ImportModule("grasp_behavior");

Release the GIL after you're done setting up and then re-acquire as needed (in your Run()).

Also, PyEval_ReleaseLock is deprecated, you should use PyEval_SaveThread in this case instead.

PyThreadState* tstate = PyEval_SaveThread();

This will save the thread state and release the GIL.

Then, right before you start finalizing the interpreter, do this:

PyEval_RestoreThread(tstate);

passing the return value of the PyEval_SaveThread call.

In Run(), you should use PyGILState_Ensure and PyGILState_Release, as you do now, but you should think about C++ exceptions. Right now PyGILState_Release will not be called if Run() throws.

One nice property of the PyGILState calls is that you can use them no matter if the GIL is acquired or not and they will do the right thing, unlike older APIs.

Also, you should initialize the interpreter once at startup in the main thread (before other threads are started) and finalize after shutting down all threads but the main one.

鯉魚旗 2025-01-03 11:45:12

Boost 是否提供了与 pthread_once() 函数等效的道德功能,该函数允许某些初始化任务仅运行一次,无论有多少线程尝试同时运行它?如果这是我要调试的问题,我会尝试保护 PyImport_ImportModule 防止多个线程中的多个调用,并使用标准工具来实现这将是我的第一次尝试。

Does Boost provide a moral equivalent to the pthread_once() function that allows some initialization task to be run exactly once, no matter how many threads try to run it simultaneously? If this were my problem to debug I'd try to guard PyImport_ImportModule against multiple calls in multiple threads and using a standard tool for that would be my first attempt.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文