【问题标题】:Boost.Python: Wrap functions to release the GILBoost.Python:包装函数以释放 GIL
【发布时间】:2013-09-08 05:25:06
【问题描述】:

我目前正在使用 Boost.Python,希望得到一些帮助来解决一个棘手的问题。

上下文

当一个 C++ 方法/函数暴露给 Python 时,它需要释放 GIL(全局解释器锁)以让其他线程使用解释器。这样,当 python 代码调用 C++ 函数时,解释器可以被其他线程使用。 现在,每个 C++ 函数看起来像这样:

// module.cpp
int myfunction(std::string question)
{
    ReleaseGIL unlockGIL;
    return 42;
}

为了将它传递给 boost python,我这样做:

// python_exposure.cpp
BOOST_PYTHON_MODULE(PythonModule)
{
    def("myfunction", &myfunction);
}

问题

此方案运行良好,但它暗示module.cpp 无缘无故依赖Boost.Python。理想情况下,只有python_exposure.cpp 应该依赖于Boost.Python

解决方案?

我的想法是使用Boost.Function 来包装函数调用,如下所示:

// python_exposure.cpp
BOOST_PYTHON_MODULE(PythonModule)
{
    def("myfunction", wrap(&myfunction));
}

wrap 将负责在调用myfunction 期间解锁 GIL。这种方法的问题是wrap 需要与myfunction 具有相同的签名,这几乎意味着重新实现Boost.Function...

如果有人对这个问题有任何建议,我将非常感激。

【问题讨论】:

  • 我从 e.tadeu 找到了一个很好的答案:stackoverflow.com/questions/2135457/…
  • 虽然它应该是安全的,但将仿函数公开为方法不是officially supported。支持的方法是公开一个委托给成员函数的非成员函数。
  • 这是一个非常有趣的观点。我想知道他们的实施缺少什么。
  • 我也发现自己很感兴趣,所以决定深入研究代码。我在下面的答案中包含了我的发现,以及一个可能可重复使用的解决您的问题的解决方案。

标签: c++ python function boost boost-python


【解决方案1】:

将仿函数公开为方法不是officially supported。支持的方法是公开一个委托给成员函数的非成员函数。但是,这可能会导致大量样板代码。

据我所知,Boost.Python 的实现并未明确排除函子,因为它允许将 python::object 的实例作为方法公开。但是,Boost.Python 确实对作为方法公开的对象类型提出了一些要求:

  • 函子是 CopyConstructible。
  • 函子是可调用的。 IE。实例o 可以调用o(a1, a2, a3)
  • 调用签名必须在运行时作为元数据可用。 Boost.Python 调用boost::python::detail::get_signature() 函数来获取这个元数据。元数据在内部用于设置正确的调用,以及从 Python 分派到 C++。

后一个要求是它变得复杂的地方。出于某种我不清楚的原因,Boost.Python 通过限定 ID 调用 get_signature(),从而阻止了参数相关查找。因此,get_signature() 的所有候选者必须在调用模板的定义上下文之前声明。例如,唯一考虑的get_signature() 重载是在调用它的模板定义之前声明的重载,例如class_def()make_function()。为解决此问题,在 Boost.Python 中启用仿函数时,必须在包含 Boost.Python 之前提供 get_signature() 重载,或显式提供表示签名的元序列到 make_function()


让我们通过一些启用仿函数支持的示例,以及提供支持守卫的仿函数。我选择不使用 C++11 特性。因此,将会有一些样板代码可以通过可变参数模板减少。此外,所有示例都将使用提供两个非成员函数和一个具有两个成员函数的spam 类的相同模型:

/// @brief Mockup class with member functions.
class spam
{
public:
  void action()
  {
    std::cout << "spam::action()"  << std::endl;
  }

  int times_two(int x)
  {
    std::cout << "spam::times_two()" << std::endl;
    return 2 * x;
  }
};

// Mockup non-member functions.
void action()
{
  std::cout << "action()"  << std::endl;
}

int times_two(int x)
{
  std::cout << "times_two()" << std::endl;
  return 2 * x;
}

启用boost::function

preferred syntax 用于Boost.Function 时,可以使用Boost.FunctionTypes 将签名分解为满足Boost.Python 要求的元数据。下面是一个完整的示例,使 boost::function 函子可以作为 Boost.Python 方法公开:

#include <iostream>

#include <boost/function.hpp>
#include <boost/function_types/components.hpp>

namespace boost  {
namespace python {
namespace detail {
// get_signature overloads must be declared before including
// boost/python.hpp.  The declaration must be visible at the
// point of definition of various Boost.Python templates during
// the first phase of two phase lookup.  Boost.Python invokes the
// get_signature function via qualified-id, thus ADL is disabled.

/// @brief Get the signature of a boost::function.
template <typename Signature>
inline typename boost::function_types::components<Signature>::type
get_signature(boost::function<Signature>&, void* = 0)
{
  return typename boost::function_types::components<Signature>::type();
}

} // namespace detail
} // namespace python
} // namespace boost

#include <boost/python.hpp>

/// @brief Mockup class with member functions.
class spam
{
public:
  void action()
  {
    std::cout << "spam::action()"  << std::endl;
  }

  int times_two(int x)
  {
    std::cout << "spam::times_two()" << std::endl;
    return 2 * x;
  }
};

// Mockup non-member functions.
void action()
{
  std::cout << "action()"  << std::endl;
}

int times_two(int x)
{
  std::cout << "times_two()" << std::endl;
  return 2 * x;
}

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;

  // Expose class and member-function.
  python::class_<spam>("Spam")
    .def("action",  &spam::action)
    .def("times_two", boost::function<int(spam&, int)>(
        &spam::times_two))
    ;

  // Expose non-member function.
  python::def("action",  &action);
  python::def("times_two", boost::function<int()>(
      boost::bind(&times_two, 21)));
}

及其用法:

>>> import example
>>> spam = example.Spam()
>>> spam.action()
spam::action()
>>> spam.times_two(5)
spam::times_two()
10
>>> example.action()
action()
>>> example.times_two()
times_two()
42

当提供将调用成员函数的仿函数时,提供的签名需要是等效的非成员函数。在这种情况下,int(spam::*)(int) 变为 int(spam&amp;, int)

// ...
  .def("times_two", boost::function<int(spam&, int)>(
        &spam::times_two))
  ;

此外,参数可以使用boost::bind 绑定到函子。例如,调用example.times_two() 不必提供参数,因为21 已经绑定到函子。

python::def("times_two", boost::function<int()>(
    boost::bind(&times_two, 21)));

带保护的自定义函子

扩展上面的示例,可以启用自定义函子类型以与 Boost.Python 一起使用。让我们创建一个名为 guarded_function 的仿函数,它将使用 RAII,仅在 RAII 对象的生命周期内调用包装函数。

/// @brief Functor that will invoke a function while holding a guard.
///        Upon returning from the function, the guard is released.
template <typename Signature,
          typename Guard>
class guarded_function
{
public:

  typedef typename boost::function_types::result_type<Signature>::type
      result_type;

  template <typename Fn>
  guarded_function(Fn fn)
    : fn_(fn)
  {}

  result_type operator()()
  {
    Guard g;
    return fn_();
  }

  // ... overloads for operator()

private:
  boost::function<Signature> fn_;
};

guarded_function 提供与 Python with 语句类似的语义。因此,为了与 Boost.Python API 名称选择保持一致,with() C++ 函数将提供一种创建函子的方法。

/// @brief Create a callable object with guards.
template <typename Guard,
          typename Fn>
boost::python::object
with(Fn fn)
{
   return boost::python::make_function(
     guarded_function<Guard, Fn>(fn), ...);
}

这允许暴露功能,这些功能将以非侵入性的方式与警卫一起运行:

class no_gil; // Guard

// ...
  .def("times_two", with<no_gil>(&spam::times_two))
  ;

此外,with() 函数提供推断函数签名的能力,允许将元数据签名显式提供给 Boost.Python,而不必重载 boost::python::detail::get_signature()

这是完整的示例,使用了两种 RAII 类型:

  • no_gil:在构造函数中释放 GIL,在析构函数中重新获取 GIL。
  • echo_guard:在构造函数和析构函数中打印。
#include <iostream>

#include <boost/function.hpp>
#include <boost/function_types/components.hpp>
#include <boost/function_types/function_type.hpp>
#include <boost/function_types/result_type.hpp>
#include <boost/python.hpp>
#include <boost/tuple/tuple.hpp>

namespace detail {

/// @brief Functor that will invoke a function while holding a guard.
///        Upon returning from the function, the guard is released.
template <typename Signature,
          typename Guard>
class guarded_function
{
public:

  typedef typename boost::function_types::result_type<Signature>::type
      result_type;

  template <typename Fn>
  guarded_function(Fn fn)
    : fn_(fn)
  {}

  result_type operator()()
  {
    Guard g;
    return fn_();
  }

  template <typename A1>
  result_type operator()(A1 a1)
  {
    Guard g;
    return fn_(a1);
  }

  template <typename A1, typename A2>
  result_type operator()(A1 a1, A2 a2)
  {
    Guard g;
    return fn_(a1, a2);
  }

private:
  boost::function<Signature> fn_;
};

/// @brief Provides signature type.
template <typename Signature>
struct mpl_signature
{
  typedef typename boost::function_types::components<Signature>::type type;
};

// Support boost::function.
template <typename Signature>
struct mpl_signature<boost::function<Signature> >:
  public mpl_signature<Signature>
{};

/// @brief Create a callable object with guards.
template <typename Guard,
          typename Fn,
          typename Policy>
boost::python::object with_aux(Fn fn, const Policy& policy)
{
  // Obtain the components of the Fn.  This will decompose non-member
  // and member functions into an mpl sequence.
  //   R (*)(A1)    => R, A1
  //   R (C::*)(A1) => R, C*, A1
  typedef typename mpl_signature<Fn>::type mpl_signature_type;

  // Synthesize the components into a function type.  This process
  // causes member functions to require the instance argument.
  // This is necessary because member functions will be explicitly
  // provided the 'self' argument.
  //   R, A1     => R (*)(A1)
  //   R, C*, A1 => R (*)(C*, A1)
  typedef typename boost::function_types::function_type<
      mpl_signature_type>::type signature_type;

  // Create a callable boost::python::object that delegates to the
  // guarded_function.
  return boost::python::make_function(
    guarded_function<signature_type, Guard>(fn),
    policy, mpl_signature_type());
}

} // namespace detail

/// @brief Create a callable object with guards.
template <typename Guard,
          typename Fn,
          typename Policy>
boost::python::object with(const Fn& fn, const Policy& policy)
{
  return detail::with_aux<Guard>(fn, policy);
}

/// @brief Create a callable object with guards.
template <typename Guard,
          typename Fn>
boost::python::object with(const Fn& fn)
{
  return with<Guard>(fn, boost::python::default_call_policies());
}

/// @brief Mockup class with member functions.
class spam
{
public:
  void action()
  {
    std::cout << "spam::action()"  << std::endl;
  }

  int times_two(int x)
  {
    std::cout << "spam::times_two()" << std::endl;
    return 2 * x;
  }
};

// Mockup non-member functions.
void action()
{
  std::cout << "action()"  << std::endl;
}

int times_two(int x)
{
  std::cout << "times_two()" << std::endl;
  return 2 * x;
}

/// @brief Guard that will unlock the GIL upon construction, and
///        reacquire it upon destruction.
struct no_gil
{
public:
  no_gil()  { state_ = PyEval_SaveThread(); 
              std::cout << "no_gil()" << std::endl; }
  ~no_gil() { std::cout << "~no_gil()" << std::endl;
              PyEval_RestoreThread(state_); }
private:
  PyThreadState* state_;
};

/// @brief Guard that prints to std::cout.
struct echo_guard 
{
  echo_guard()  { std::cout << "echo_guard()" << std::endl;  }
  ~echo_guard() { std::cout << "~echo_guard()" << std::endl; }
};

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;

  // Expose class and member-function.
  python::class_<spam>("Spam")
    .def("action", &spam::action)
    .def("times_two", with<no_gil>(&spam::times_two))
    ;

  // Expose non-member function.
  python::def("action", &action);
  python::def("times_two", with<boost::tuple<no_gil, echo_guard> >(
      &times_two));
}

及其用法:

>>> import example
>>> spam = example.Spam()
>>> spam.action()
spam::action()
>>> spam.times_two(5)
no_gil()
spam::times_two()
~no_gil()
10
>>> example.action()
action()
>>> example.times_two(21)
no_gil()
echo_guard()
times_two()
~echo_guard()
~no_gil()
42

注意如何使用容器类型提供多个保护,例如boost::tuple

  python::def("times_two", with<boost::tuple<no_gil, echo_guard> >(
      &times_two));

在 Python 中调用时,example.times_two(21) 会产生以下输出:

no_gil()
echo_guard()
times_two()
~echo_guard()
~no_gil()
42

【讨论】:

  • 首先,我真的很感谢这个完美而清晰的解释,以及你没有在其中使用 C++11 的事实(当然没有反对它)。这个解决方案是惊人的!它干净通用,非常感谢!它完美地解决了我的问题,我相信我会多次重复使用该代码。警卫应该在门口,而不是里面:)
  • 您好 Tanner,我在使用您的代码时遇到了这个错误的签名问题。我在下面发布了我对这个问题的解决方案。也许我在你的解释中遗漏了一些东西。无论如何,再次感谢您的回答,它对我帮助很大!
【解决方案2】:

如果有人感兴趣,我在使用 Tanner Sansbury 的最终工作示例时遇到了一个小问题。由于某种原因,我仍然遇到他提到的关于在最终生成的boost::function 中签名错误的问题:

// example for spam::times_two:
//   correct signature (manual)
int (spam::*, int)
//   wrong signature (generated in the `guarded_function` wrapper)
int (spam&, int)

即使重载boost::python::detail::get_signature()。对此负责的是boost::function_types::components;它有一个默认模板参数ClassTranform = add_reference&lt;_&gt;,用于创建此类引用。为了解决这个问题,我简单地将mpl_signature 结构更改如下:

// other includes
# include <boost/type_traits/add_pointer.hpp>
# include <boost/mpl/placeholders.hpp>

template <typename Signature> struct mpl_signature
{
  typedef typename boost::function_types::components<Signature,
                boost::add_pointer<boost::mpl::placeholders::_> >::type type;
};

template <typename Signature> struct mpl_signature<boost::function<Signature> >
{
  typedef typename boost::function_types::components<Signature>::type type;
};

现在一切都像魅力一样运作。

如果有人可以确认这确实是正确的解决方案,我会很感兴趣 :)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-11-02
    • 1970-01-01
    • 2011-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-12
    • 1970-01-01
    相关资源
    最近更新 更多