我有一個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 ) –