2016-12-20 4 views
2

У меня есть приложение Python, которое вызывает библиотеку python с расширением C++, и все это работает. Тем не менее, у меня есть сценарий обратного вызова C++ для Python, где C++ из потока boost вызывает python, и я получаю нарушение доступа на стороне C++. Если я делаю точно такой же обратный вызов, используя поток python, он отлично работает. Поэтому я подозреваю, что не могу просто вызвать Python из C++, используя поток boost, но вам нужно сделать что-то дополнительное для его работы?Как вызвать Python из потока boost?

+1

Возможный дубликат [PyEval \ _InitThreads в Python 3: Как/когда его называть? (сага продолжается ad nauseum)] (http://stackoverflow.com/questions/15470367/pyeval-initthreads-in-python-3-how-when-to-call-it-saga-continues-ad-naus) –

ответ

6

Наиболее вероятным виновником является то, что Global Interpreter Lock (GIL) не удерживается нитью при вызове кода Python, что приводит к неопределенному поведению. Проверьте все пути, которые делают прямые или косвенные вызовы Python, приобретают GIL перед вызовом кода Python.


GIL - это мьютекс вокруг интерпретатора CPython. Этот мьютекс предотвращает параллельные операции над объектами Python. Таким образом, в любой момент времени максимум одного потока, тот, который приобрел GIL, разрешен для выполнения операций над объектами Python. Когда присутствуют несколько потоков, вызывать код Python, не удерживая GIL, приводит к неопределенному поведению.

Потоки C или C++ иногда упоминаются как чужие потоки в документации Python. У интерпретатора Python нет возможности управлять чужой нитью. Поэтому чужие потоки отвечают за управление GIL, чтобы разрешать параллельное или параллельное выполнение с потоками Python. Нужно тщательно рассмотреть:

  • Стек разматывается, так как Boost.Python может вызывать исключение.
  • Косвенных Звонки на Python, такие как копировальные конструкторы или деструкторы

Одно из решений, чтобы обернуть обратные вызовы Python с помощью пользовательского типа, осознающее управления GIL.


Использование RAII-style класса для управления GIL обеспечивает элегантное исключение безопасного решения. Например, со следующим классом with_gil, когда создается объект with_gil, вызывающий поток получает GIL. Когда объект with_gil разрушен, он восстанавливает состояние GIL.

/// @brief Guard that will acquire the GIL upon construction, and 
///  restore its state upon destruction. 
class with_gil 
{ 
public: 
    with_gil() { state_ = PyGILState_Ensure(); } 
    ~with_gil() { PyGILState_Release(state_); } 

    with_gil(const with_gil&)   = delete; 
    with_gil& operator=(const with_gil&) = delete; 
private: 
    PyGILState_STATE state_; 
}; 

И его использование:

{ 
    with_gil gil;      // Acquire GIL. 
    // perform Python calls, may throw 
}         // Restore GIL. 

С будучи в состоянии управлять GIL через with_gil, следующий шаг заключается в создании функтор, который правильно управляет GIL. Следующий py_callable класс будет обернуть boost::python::object и приобрести GIL для всех путей, в которых Python кода вызываются:

/// @brief Helper type that will manage the GIL for a python callback. 
/// 
/// @detail GIL management: 
///   * Acquire the GIL when copying the `boost::python` 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_callable` is invoked (operator()), it will acquire 
///    the GIL then delegate to the managed `python::object` 
class py_callable 
{ 
public: 

    /// @brief Constructor that assumes the caller has the GIL locked. 
    py_callable(const boost::python::object& object) 
    { 
    with_gil gil; 
    object_.reset(
     // GIL locked, so it is safe to copy. 
     new boost::python::object{object}, 
     // Use a custom deleter to hold GIL when the object is deleted. 
     [](boost::python::object* object) 
     { 
     with_gil gil; 
     delete object; 
     }); 
    } 

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

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

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

Управляя boost::python::object на свободном пространстве, можно свободно копировать shared_ptr без необходимости удерживать GIL. Это позволяет нам безопасно использовать созданный по умолчанию экземпляр-конструктор, оператор присваивания, деструктор и т. Д.

Можно было бы использовать py_callable следующим образом:

// thread 1 
boost::python::object object = ...; // GIL must be held. 
py_callable callback(object);  // GIL no longer required. 
work_queue.post(callback); 

// thread 2 
auto callback = work_queue.pop(); // GIL not required. 
// Invoke the callback. If callback is `py_callable`, then it will 
// acquire the GIL, invoke the wrapped `object`, then release the GIL. 
callback(...); 

Вот полный пример demonstrating, имеющий расширение Python вызова объект Python в качестве обратного вызова из C++ нить:

#include <memory> // std::shared_ptr 
#include <thread> // std::this_thread, std::thread 
#include <utility> // std::forward 
#include <boost/python.hpp> 

/// @brief Guard that will acquire the GIL upon construction, and 
///  restore its state upon destruction. 
class with_gil 
{ 
public: 
    with_gil() { state_ = PyGILState_Ensure(); } 
    ~with_gil() { PyGILState_Release(state_); } 

    with_gil(const with_gil&)   = delete; 
    with_gil& operator=(const with_gil&) = delete; 
private: 
    PyGILState_STATE state_; 
}; 

/// @brief Helper type that will manage the GIL for a python callback. 
/// 
/// @detail GIL management: 
///   * Acquire the GIL when copying the `boost::python` 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_callable` is invoked (operator()), it will acquire 
///    the GIL then delegate to the managed `python::object` 
class py_callable 
{ 
public: 

    /// @brief Constructor that assumes the caller has the GIL locked. 
    py_callable(const boost::python::object& object) 
    { 
    with_gil gil; 
    object_.reset(
     // GIL locked, so it is safe to copy. 
     new boost::python::object{object}, 
     // Use a custom deleter to hold GIL when the object is deleted. 
     [](boost::python::object* object) 
     { 
     with_gil gil; 
     delete object; 
     }); 
    } 

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

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

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

BOOST_PYTHON_MODULE(example) 
{ 
    // Force the GIL to be created and initialized. The current caller will 
    // own the GIL. 
    PyEval_InitThreads(); 

    namespace python = boost::python; 
    python::def("call_later", 
    +[](int delay, python::object object) { 
     // Create a thread that will invoke the callback. 
     std::thread thread(+[](int delay, py_callable callback) { 
     std::this_thread::sleep_for(std::chrono::seconds(delay)); 
     callback("spam"); 
     }, delay, py_callable{object}); 
     // Detach from the thread, allowing caller to return. 
     thread.detach(); 
    }); 
} 

Интерактивное использование:

>>> import time 
>>> import example 
>>> def shout(message): 
...  print message.upper() 
... 
>>> example.call_later(1, shout) 
>>> print "sleeping"; time.sleep(3); print "done sleeping" 
sleeping 
SPAM 
done sleeping 
+0

Wow спасибо, очень тщательный ответ! Я проверю его и вернусь к вам ... –

+0

Этот ответ WOOOOORRRRKKKSSSSSS !!! –

Смежные вопросы