Boost.Python: функции Wrap для выпуска GIL

В настоящее время я работаю с Boost.Python и хочу помочь решить сложную проблему.

контекст

Когда метод / функция C ++ подвергается воздействию Python, ему необходимо освободить GIL (Global Interpreter Lock), чтобы другие потоки использовали интерпретатор. Таким образом, когда код 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 . Проблема с этим методом заключается в том, что myfunction должна иметь ту же подпись, что и myfunction которая в значительной степени означает повторное внедрение Boost.Function

Я был бы очень благодарен, если бы у кого-то было предложение по этой проблеме.

2 Solutions collect form web for “Boost.Python: функции Wrap для выпуска GIL”

Предоставление функторов как методов официально не поддерживается . Поддерживаемый подход заключается в том, чтобы выставить функцию, не являющуюся членом, которая делегирует функции-члену. Однако это может привести к большому количеству кода шаблона.

Насколько я могу судить, реализация Boost.Python явно не исключает функторов, поскольку позволяет экземплярам python::object быть выставлен как метод. Однако Boost.Python устанавливает некоторые требования к типу объекта, который отображается в виде метода:

  • Функтор CopyConstructible.
  • Функтор является вызываемым. Т.е. экземпляр o можно назвать o(a1, a2, a3) .
  • Подпись вызова должна быть доступна в виде метаданных во время выполнения. Boost.Python вызывает функцию boost::python::detail::get_signature() для получения этих метаданных. Метаданные используются внутренне для настройки правильного вызова, а также для отправки с Python на C ++.

Последнее требование – там, где оно становится сложным. По какой-то причине это не сразу мне кажется, Boost.Python вызывает get_signature() через идентификатор с квалификацией, предотвращая зависящий от аргумента поиск. Поэтому все кандидаты на get_signature() должны быть объявлены перед контекстом определения вызывающего шаблона. Например, единственными перегрузками для get_signature() , которые считаются, являются те, которые были объявлены перед определением вызывающих его шаблонов, таких как class_ , def() и make_function() . Чтобы учесть это поведение, при включении функтора в Boost.Python необходимо предоставить перегрузку get_signature() до включения Boost.Python или явно предоставить мета-последовательность, представляющую подпись, для make_function() .


Давайте рассмотрим некоторые примеры поддержки функций functor, а также предоставим функторам, которые поддерживают защиту. Я решил не использовать возможности 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. Позволяет создать функтор, называемый 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. Таким образом, чтобы сохранить варианты выбора имени 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 

Если кто-то заинтересован, у меня была небольшая проблема с кодом Тэннера Сансбери при использовании его последнего рабочего примера. По какой-то причине у меня все еще была проблема, о которой он упомянул о наличии неправильной сигнатуры в окончательной сгенерированной 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<_> который создает эту ссылку класса. Чтобы исправить это, я просто изменил структуру 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; }; 

И теперь все работает как шарм.

Если кто-то может подтвердить, что это действительно правильное решение, мне было бы интересно 🙂

  • все устаревшие данные компиляции swig + python + mingw устарели?
  • Правильный способ приведения numpy.matrix в C двойной указатель
  • Python.h: Нет такого файла или каталога
  • Публикация приложения MVC, использующего скрипт python
  • Обработка полиморфных исключений: как уловить исключение подкласса?
  • статический класс openCL, который не был правильно выпущен в модуле python, используя boost.python
  • python bindings, как это работает?
  • Как запустить файл python, который считывается в std :: string, используя PyRun
  • Python - лучший язык программирования в мире.