Boost.Python以及增压信号2:分段错误

2024-09-29 22:32:54 发布

您现在位置:Python中文网/ 问答频道 /正文

我在整合上有问题增压信号2在我现有的C++ LIB中,我已经暴露了boost.python. 在

我有一个用std::shared_ptr向python公开的类。 这个类应该能够在某些事件上发出一些信号。 因此,我公开了一个connect_slot函数,它以boost::python::object作为参数。如果我在连接一个插槽后直接发出一个信号,一切都会正常工作,但是如果类在以后发出信号,我就会收到分段错误。在

我认为这可能与c++库中的线程有关(它也在使用boos::asio等)

下面是一些代码片段:

我的class.h:

public:
    typedef boost::signals2::signal<void (std::shared_ptr<int>)> signal_my_sig;
    void connect_slot(boost::python::object const & slot);

private:
    signal_my_sig    m_sig;

在我的class.cpp以下内容:

^{pr2}$

我用一个定制的python函数调用python中的MyClass::connect_slot函数,如下所示:

def testfunc(some_int):
    print("slot called")

m = myext.MyClass()
m.connect_slot(testfunc)

MyClass::some_later_event中引发的分段错误的回溯(使用gdb)如下所示:

[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff3c37700 (LWP 20634)]

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff3c37700 (LWP 20634)]
0x00000000004f7480 in PyObject_Call ()
(gdb) 
(gdb) backtrace
#0  0x00000000004f7480 in PyObject_Call ()
#1  0x00000000004f7aa6 in PyEval_CallObjectWithKeywords ()
#2  0x000000000049bd84 in PyEval_CallFunction ()
#3  0x00007ffff5375d9f in boost::python::call<boost::python::api::object, int>
(callable=0x7ffff7ed4578, a0=@0x7ffff3c35b34: 5)
at /usr/local/boost_1_55_0/boost/python/call.hpp:66
#4  0x00007ffff5374b81 in boost::python::api::object_operators<boost::python::api::object>::operator()<int> (this=0x9e3bf0, a0=@0x7ffff3c35b34: 5)
at /usr/local/boost_1_55_0/boost/python/object_call.hpp:19
#5  0x00007ffff5373658 in boost::detail::function::void_function_obj_invoker1<boost::python::api::object, void, int>::invoke (function_obj_ptr=..., a0=5)
at /usr/local/boost_1_55_0/boost/function/function_template.hpp:153
#6  0x00007ffff5378a3c in boost::function1<void, int>::operator() (
this=0x9e3be8, a0=5)
at /usr/local/boost_1_55_0/boost/function/function_template.hpp:767
#7  0x00007ffff53781f9 in boost::signals2::detail::call_with_tuple_args<boost::signals2::detail::void_type>::m_invoke<boost::function<void (int)>, 0u, int&>(void*, boost::function<void (int)>&, boost::signals2::detail::unsigned_meta_array<0u>, std::tuple<int&>) const (this=0x7ffff3c35c7f, func=..., args=...)
at /usr/local/boost_1_55_0/boost/signals2/detail/variadic_slot_invoker.hpp:92

有什么想法吗?


Tags: insignalobjectusrlocalconnectfunctionat
2条回答

感谢Tanner Sansbury在this帖子上链接了他的答案。这解决了我的问题,只是我不能调用接受参数的信号。在

我通过编辑py_slot类解决了这个问题:

struct py_slot {
    public:
        /// @brief Constructor that assumes the caller has the GIL locked.
        py_slot(const boost::python::object& object)
            : object_(new boost::python::object(object),   // GIL locked, so     copy.
            py_deleter<boost::python::object>()) // Delete needs GIL.
            {}

        void operator()(SomeParamClass param) {
            // Lock the gil as the python object is going to be invoked.
            gil_lock lock;

            (*object_)(param);

    private:
        boost::shared_ptr<boost::python::object> object_;
};

boost::bind调用如下所示:

^{pr2}$

如果从一个不显式管理Global Interpreter Lock(吉尔)的C++线程调用^ {CD1>},则会导致未定义的行为。在


< H2> Python和C++线程。

让我们考虑C++线程与Python交互的情况。例如,可以设置一个C++线程,通过^ {CD3}}在一段时间后调用^ {< CD2> }的信号。在

这个例子可能会涉及很多,所以让我们从基础知识开始:Python的GIL。简而言之,GIL是围绕着翻译的互斥体。如果一个线程正在做任何影响python托管对象的引用计数的操作,那么它需要获得GIL。在GDB回溯中增压信号2library可能试图在没有GIL的情况下调用Python对象,导致崩溃。虽然管理GIL相当简单,但它很快就会变得复杂。在

首先,该模块需要Python初始化GIL进行线程化。在

BOOST_PYTHON_MODULE(example)
{
  PyEval_InitThreads(); // Initialize GIL to support non-python threads.
  // ...
}

为了方便起见,让我们创建一个简单的类来帮助通过作用域管理GIL:

^{pr2}$

让我们确认C++线程何时需要吉尔:

  • boost::signals2::signal可以创建连接对象的附加副本,就像调用信号时一样。在
  • 调用通过boost::signals2::signal连接的Python对象。回调肯定会影响python对象。例如,提供给__call__方法的self参数将增加或减少对象的引用计数。在

MyClass类。

以下是基于原始代码的基本模型类:

/// @brief Mockup class.
class MyClass
{
public:
  /// @brief Connect a slot to the signal.
  template <typename Slot>
  void connect_slot(const Slot& slot)
  {
    signal_.connect(slot);
  }

  /// @brief Send an event to the signal.
  void event(int value)
  {
    signal_(value);
  }

private:
  boost::signals2::signal<void(int)> signal_;
};

作为C++线程,可能调用^ {CD2>}的信号,其生存期至少为线程的长度。一个很好的人选是Boost.Python使用boost::shared_ptr管理MyClass。在

BOOST_PYTHON_MODULE(example)
{
  PyEval_InitThreads(); // Initialize GIL to support non-python threads.

  namespace python = boost::python;
  python::class_<MyClass, boost::shared_ptr<MyClass>,
                 boost::noncopyable>("MyClass")
    .def("event", &MyClass::event)
    // ...
    ;
}

boost::signals2::signal与python对象交互。

boost::signals2::signal在调用时可能会生成副本。另外,可能有连接到信号的C++插槽,所以在调用Python插槽时只锁定吉尔是理想的。但是,signal并没有提供钩子来允许我们在创建slot副本或调用slot之前获取GIL。在

为了避免signal创建boost::python::object槽的副本,可以使用一个包装类来创建boost::python::object的副本,以便引用计数保持准确,并通过shared_ptr管理副本。这允许signal自由地创建shared_ptr的副本,而不是在没有GIL的情况下复制{}。在

这个GIL安全槽可以封装在helper类中。在

/// @brief Helepr type that will manage the GIL for a python slot.
///
/// @detail GIL management:
///           * Caller must own GIL when constructing py_slot, as 
///             the python::object will be copy-constructed (increment
///             reference to the object)
///           * The newly constructed python::object will be managed
///             by a shared_ptr.  Thus, it may be copied without owning
///             the GIL.  However, a custom deleter will acquire the
///             GIL during deletion.
///           * When py_slot is invoked (operator()), it will acquire
///             the GIL then delegate to the managed python::object.
struct py_slot
{
public:

  /// @brief Constructor that assumes the caller has the GIL locked.
  py_slot(const boost::python::object& object)
    : object_(
        new boost::python::object(object),  // GIL locked, so copy.
        [](boost::python::object* object)   // Delete needs GIL.
        {
          gil_lock lock;
          delete object;
        }
      )
  {}

  // Use default copy-constructor and assignment-operator.
  py_slot(const py_slot&) = default;
  py_slot& operator=(const py_slot&) = default;

  template <typename ...Args>
  void operator()(Args... args)
  {
    // Lock the GIL as the python object is going to be invoked.
    gil_lock lock;
    (*object_)(args...); 
  }

private:
  boost::shared_ptr<boost::python::object> object_;
};

一个helper函数将向Python公开,以帮助调整类型。在

/// @brief MyClass::connect_slot helper.
template <typename ...Args>
void MyClass_connect_slot(
  MyClass& self,
  boost::python::object object)
{
  py_slot slot(object); // Adapt object to a py_slot for GIL management.

  // Using a lambda here allows for the args to be expanded automatically.
  // If bind was used, the placeholders would need to be explicitly added.
  self.connect_slot([slot](Args... args) mutable { slot(args...); });
}

更新后的绑定公开了helper函数:

python::class_<MyClass, boost::shared_ptr<MyClass>,
               boost::noncopyable>("MyClass")
  .def("connect_slot", &MyClass_connect_slot<int>)
  .def("event",        &MyClass::event)
  // ...
  ;

线本身。

线程的功能相当基本:它休眠然后调用信号。然而,理解GIL的背景是很重要的。在

/// @brief Sleep then invoke an event on MyClass.
template <typename ...Args>
void MyClass_event_in_thread(
  boost::shared_ptr<MyClass> self,
  unsigned int seconds,
  Args... args)
{
  // Sleep without the GIl.
  std::this_thread::sleep_for(std::chrono::seconds(seconds));

  // We do not want to hold the GIL while invoking or copying 
  // C++-specific slots connected to the signal.  Thus, it is the 
  // responsibility of python slots to manage the GIL via the 
  // py_slot wrapper class.
  self->event(args...);
}

/// @brief Function that will be exposed to python that will create
///        a thread to call the signal.
template <typename ...Args>
void MyClass_event_in(
  boost::shared_ptr<MyClass> self,
  unsigned int seconds,
  Args... args)
{
  // The caller may or may not have the GIL.  Regardless, spawn off a 
  // thread that will sleep and then invoke an event on MyClass.  The
  // thread will not be joined so detach from it.  Additionally, as
  // shared_ptr is thread safe, copies of it can be made without the
  // GIL.
  std::thread(&MyClass_event_in_thread<Args...>, self, seconds, args...)
      .detach();
}

注意,MyClass_event_in_thread可以表示为lambda,但是在lambda中解压模板包在某些编译器上不起作用。

并更新MyClass绑定。在

python::class_<MyClass, boost::shared_ptr<MyClass>,
               boost::noncopyable>("MyClass")
  .def("connect_slot", &MyClass_connect_slot<int>)
  .def("event",        &MyClass::event)
  .def("event_in",     &MyClass_event_in<int>)
  ;

最终解决方案如下:

#include <thread> // std::thread, std::chrono
#include <boost/python.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/signals2/signal.hpp>

/// @brief Mockup class.
class MyClass
{
public:
  /// @brief Connect a slot to the signal.
  template <typename Slot>
  void connect_slot(const Slot& slot)
  {
    signal_.connect(slot);
  }

  /// @brief Send an event to the signal.
  void event(int value)
  {
    signal_(value);
  }

private:
  boost::signals2::signal<void(int)> signal_;
};

/// @brief RAII class used to lock and unlock the GIL.
class gil_lock
{
public:
  gil_lock()  { state_ = PyGILState_Ensure(); }
  ~gil_lock() { PyGILState_Release(state_);   }
private:
  PyGILState_STATE state_;
};    

/// @brief Helepr type that will manage the GIL for a python slot.
///
/// @detail GIL management:
///           * Caller must own GIL when constructing py_slot, as 
///             the python::object will be copy-constructed (increment
///             reference to the object)
///           * The newly constructed python::object will be managed
///             by a shared_ptr.  Thus, it may be copied without owning
///             the GIL.  However, a custom deleter will acquire the
///             GIL during deletion.
///           * When py_slot is invoked (operator()), it will acquire
///             the GIL then delegate to the managed python::object.
struct py_slot
{
public:

  /// @brief Constructor that assumes the caller has the GIL locked.
  py_slot(const boost::python::object& object)
    : object_(
        new boost::python::object(object),  // GIL locked, so copy.
        [](boost::python::object* object)   // Delete needs GIL.
        {
          gil_lock lock;
          delete object;
        }
      )
  {}

  // Use default copy-constructor and assignment-operator.
  py_slot(const py_slot&) = default;
  py_slot& operator=(const py_slot&) = default;

  template <typename ...Args>
  void operator()(Args... args)
  {
    // Lock the GIL as the python object is going to be invoked.
    gil_lock lock;
    (*object_)(args...); 
  }

private:
  boost::shared_ptr<boost::python::object> object_;
};

/// @brief MyClass::connect_slot helper.
template <typename ...Args>
void MyClass_connect_slot(
  MyClass& self,
  boost::python::object object)
{
  py_slot slot(object); // Adapt object to a py_slot for GIL management.

  // Using a lambda here allows for the args to be expanded automatically.
  // If bind was used, the placeholders would need to be explicitly added.
  self.connect_slot([slot](Args... args) mutable { slot(args...); });
}

/// @brief Sleep then invoke an event on MyClass.
template <typename ...Args>
void MyClass_event_in_thread(
  boost::shared_ptr<MyClass> self,
  unsigned int seconds,
  Args... args)
{
  // Sleep without the GIL.
  std::this_thread::sleep_for(std::chrono::seconds(seconds));

  // We do not want to hold the GIL while invoking or copying 
  // C++-specific slots connected to the signal.  Thus, it is the 
  // responsibility of python slots to manage the GIL via the 
  // py_slot wrapper class.
  self->event(args...);
}

/// @brief Function that will be exposed to python that will create
///        a thread to call the signal.
template <typename ...Args>
void MyClass_event_in(
  boost::shared_ptr<MyClass> self,
  unsigned int seconds,
  Args... args)
{
  // The caller may or may not have the GIL.  Regardless, spawn off a 
  // thread that will sleep and then invoke an event on MyClass.  The
  // thread will not be joined so detach from it.  Additionally, as
  // shared_ptr is thread safe, copies of it can be made without the
  // GIL.
  // Note: MyClass_event_in_thread could be expressed as a lambda,
  //       but unpacking a template pack within a lambda does not work
  //       on some compilers.
  std::thread(&MyClass_event_in_thread<Args...>, self, seconds, args...)
      .detach();
}

BOOST_PYTHON_MODULE(example)
{
  PyEval_InitThreads(); // Initialize GIL to support non-python threads.

  namespace python = boost::python;
  python::class_<MyClass, boost::shared_ptr<MyClass>,
                 boost::noncopyable>("MyClass")
    .def("connect_slot", &MyClass_connect_slot<int>)
    .def("event",        &MyClass::event)
    .def("event_in",     &MyClass_event_in<int>)
    ;
}

以及测试脚本:

from time import sleep
import example

def spam1(x):
  print "spam1: ", x

def spam2(x):
  print "spam2: ", x

c = example.MyClass()
c.connect_slot(spam1)
c.connect_slot(spam2)
c.event(123)
print "Sleeping"
c.event_in(3, 321)
sleep(5)
print "Done sleeping"

结果如下:

spam1:  123
spam2:  123
Sleeping
spam1:  321
spam2:  321
Done sleeping

相关问题 更多 >

    热门问题