当前位置: 首页 > 面试题库 >

Boost.Python:包装函数以释放GIL

史洛城
2023-03-14
问题内容

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

语境

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

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

为了通过它来增强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呼叫期间,这里将负责解锁GIL
myfunction。这种方法的问题在于,wrap需要具有与myfunction几乎意味着重新实现相同的签名Boost.Function

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


问题答案:

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

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

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

后一个要求是它变得复杂。由于某些原因(我尚不了解),Boost.Pythonget_signature()通过限定ID进行调用,从而避免了依赖于参数的查找。因此,get_signature()必须在调用模板的定义上下文之前声明所有候选对象。例如,对于唯一的重载get_signature()被认为是那些模板定义之前宣布调用它,如class_def()make_function()。为了解决此问题,在Boost.Python中启用函子时,必须get_signature()在包含Boost.Python之前提供重载,或者显式提供表示签名的元序列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

当使用优选的句法对来自Boost.Function,分解成签名元数据符合的Boost.Python要求是可以做到Boost.FunctionTypes。这是一个完整的示例,使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&, 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一起使用。让我们创建一个名为RA的函子,该函子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

    include

    include

    include

    include

    include

    namespace detail {

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

    typedef typename boost::function_types::result_type ::type
    result_type;

    template
    guarded_function(Fn fn)
    : fn_(fn)

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

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

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

    private:
    boost::function fn_;
    };

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

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

    /// @brief Create a callable object with guards.
    template
    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 ::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 (fn),
    policy, mpl_signature_type());
    }

    } // namespace detail

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

    /// @brief Create a callable object with guards.
    template
    boost::python::object with(const Fn& fn)
    {
    return with (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”)
    .def(“action”, &spam::action)
    .def(“times_two”, with (&spam::times_two))
    ;

    // Expose non-member function.
    python::def(“action”, &action);
    python::def(“times_two”, with >(
    &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


 类似资料:
  • 当从Oracle datasource获得连接时,我们使用appserver(weblogic/JBoss)特定的包装器将连接包装到Oracle连接,如下面所示。如果我们不包装它,我们将无法使用Oracle的特性,如ArrayDescriptors。我们应该改变我们的应用程序,使它们同时在weblogic和JBoss中工作。 java: 每当需要连接时,我们将像下面这样调用Datasource:

  • 问题内容: 我有一个带有装饰器的函数,我正在Python Mock库的帮助下进行测试。我想用一个仅调用函数的模拟“ bypass”装饰器代替真正的装饰器。 我不知道的是如何在真正的装饰器包装功能之前应用补丁。我在补丁目标上尝试了几种不同的变体,并对补丁和导入语句重新排序,但均未成功。有任何想法吗? 问题答案: 装饰器在函数定义时应用。对于大多数功能,这是模块加载时的时间。(在其他函数中定义的函数会

  • 问题内容: 尽管这里有一个相同的问题,但是我找不到我的问题的答案,所以这里是我的问题: 我正在使用mocha和chai测试我的node js应用程序。我正在用sinion封装功能。 当我尝试运行此测试时,它给了我错误 我也尝试过 在每个描述中,但仍然给我相同的错误。 问题答案: 您应该恢复in 功能,请按以下方法尝试。

  • 我试图创建一个构建和发布管道,使用Azure Devops创建一个nuget包。我对这个不熟悉。我有以下步骤,构建步骤 每个步骤的详细信息如下图,Nuget 还原 构建解决方案 Nuget包 发布神器 我添加了一个发布管道,如下所示,发布管道 详细信息如下,Nuget Push 当我试图按下下面的nuget包时,我得到了一个如下的错误。 错误 我在nuget push中尝试了“path to pa

  • 是否可以将C函数包装成“另一个”?(当然只有宏) 类似于: 那么,上述做法安全吗?

  • 问题 你想在装饰器中给被包装函数增加额外的参数,但是不能影响这个函数现有的调用规则。 解决方案 可以使用关键字参数来给被包装函数增加额外参数。考虑下面的装饰器: from functools import wraps def optional_debug(func): @wraps(func) def wrapper(*args, debug=False, **kwargs):