我有一个Python应用程序调用C++ boost python库,它都可以工作。不过,我有一个回调C++的Python场景,其中来自boost线程的C++调用python,并在C++端获得访问冲突。如果我使用python线程完成相同的回调,它可以完美地工作。因此,我怀疑我不能使用boost线程从C++中简单地回调Python,但需要额外的工作才能使用它。如何从boost线程调用Python?
回答
最有可能的罪魁祸首是Global Interpreter Lock(GIL)在调用Python代码时未被线程占用,导致未定义的行为。验证所有直接或间接Python调用的路径,在调用Python代码之前获取GIL。
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.
与正在能够通过with_gil
管理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(...);
下面是一个完整的例子具有Python扩展调用Python对象从一个C++线程回调demonstrating:
#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
谢谢你非常透彻的回答!我会测试它并回复你... –
这个问题WOOOOORRRRKKKSSSSSS !!! –
- 1. Boost线程不调用线程函数
- 2. 如何调节Python线程?
- 3. 在单独的线程中调用boost :: python :: object作为函数
- 4. Boost线程完成回调可用
- 5. 如何使用Boost创建线程
- 6. Boost线程禁用
- 7. 从Python线程中调用的ActiveX DLL
- 8. 从C API多线程调用python
- 9. 从C线程调用Python代码
- 10. 从C++线程调用python脚本,GIL
- 11. 在线程中调用boost :: asio :: read()会调用线程或进程?
- 12. 从线程池中调用时,boost的io_service共享线程是否为请求?
- 13. 的boost ::线程加入的功能块调用线程
- 14. C++ boost ::线程,如何启动线程内的线程
- 15. 如何从新线程调用方法
- 16. 如何从非UI线程调用Snackbar.make()?
- 17. 的Python:从主线程或调用线程
- 18. boost线程池
- 19. Boost多线程
- 20. Boost线程等价于Python的threading.Event?
- 21. 如何从VBA调用python程序?
- 22. 如何从C#中的其他线程调用线程?
- 23. 如何从主线程上的线程调用方法?
- 24. Python线程调试
- 25. Python线程调度
- 26. 如何睡觉一个C++ Boost线程
- 27. 如何编译boost线程库
- 28. 如何创建迭代boost线程?
- 29. 如何取消(不终止)boost线程?
- 30. 我如何使Boost多线程?
Python 3中的PyEval \ _InitThreads可能重复:如何/何时调用它? (传奇继续广告nauseum)](http://stackoverflow.com/questions/15470367/pyeval-initthreads-in-python-3-how-when-to-call-it-the-saga-continues-ad-naus ) –