简而言之,Boost.Python 通过其 TypeWrappers 维护了 Python 参数传递语义。因此,当将 Python 中的列表传递给公开的 C++ 函数时,可以通过接受 Python 列表作为引用来创建和维护引用。boost::python::list object.
详细的答案实际上更有深度。在深入研究之前,让我先扩展一些语义以避免混淆。利用 Python 的垃圾收集和传递对象语义,一般经验法则是将 Boost.Python TypeWrappers 视为智能指针。
- 如果该函数接受列表作为
boost::python::list
对象,那么 C++ 现在就拥有了对 Python 对象的引用。 Python 列表的生命周期将延长至至少与booot::python::list
object.
- 如果Python列表被转换为不同的类型,例如
std::vector
,然后 C++ 构造了一个到 Python 列表的副本。此副本与原始列表没有关联。
下面是一个简单的 C++ 模块示例,可以传递 Python 列表、维护它的句柄并打印其内容:
#include <iostream> // std::cout
#include <utility> // std::make_pair
#include <boost/foreach.hpp>
#include <boost/python.hpp>
#include <boost/python/stl_iterator.hpp>
boost::python::list list;
/// @brief Store handle to the list.
///
/// @param pylist Python list for which a handle will be maintained.
void set(const boost::python::list& pylist)
{
// As the boost::python::list object is smart-pointer like, this
// creates a reference to the python list, rather than creating a
// copy of the python list.
list = pylist;
}
// Iterate over the current list, printing all ints.
void display()
{
std::cout << "in display" << std::endl;
typedef boost::python::stl_input_iterator<int> iterator_type;
BOOST_FOREACH(const iterator_type::value_type& data,
std::make_pair(iterator_type(list), // begin
iterator_type())) // end
{
std::cout << data << std::endl;
}
}
BOOST_PYTHON_MODULE(example) {
namespace python = boost::python;
python::def("set", &set);
python::def("display", &display);
}
及其用法:
>>> import example
>>>
>>> x = range(2)
>>> x
[0, 1]
>>> example.set(x)
>>> example.display()
in display
0
1
>>> x[:] = range(7, 10)
>>> example.display()
in display
7
8
9
问题中引入的一个复杂性是希望随时读取 C++ 中的 Python 列表。在最复杂的情况下,任何时间可以在 C++ 线程内的任何时间点发生。
让我们从基础开始:Python全局解释器锁(吉尔)。简而言之,GIL 是解释器周围的互斥体。如果一个线程正在做任何影响 python 托管对象的引用计数的事情,那么它需要获取 GIL。有时引用计数不是很透明,请考虑:
typedef boost::python::stl_input_iterator<int> iterator_type;
iterator_type iterator(list);
虽然boost::python::stl_input_iterator接受list
作为常量引用,它从构造函数内创建对 Python 列表的引用。
在前面的示例中,由于没有 C++ 线程,因此所有操作都在获取 GIL 时发生。然而,如果display()
可以随时从 C++ 中调用,然后需要进行一些设置。
首先,该模块需要让 Python 初始化 GIL 以进行线程处理。
BOOST_PYTHON_MODULE(example) {
PyEval_InitThreads(); // Initialize GIL to support non-python threads.
...
}
为了方便起见,我们创建一个简单的类来帮助管理 GIL:
/// @brief RAII class used to lock and unlock the GIL.
class gil_lock
{
public:
gil_lock() { state_ = PyGILState_Ensure(); }
~gil_lock() { PyGILState_Release(state_); }
private:
PyGILState_STATE state_;
};
为了显示与 C++ 线程的交互,让我们向模块添加功能,该功能将允许应用程序安排显示列表内容的延迟。
/// @brief Entry point for delayed display thread.
///
/// @param Delay in seconds.
void display_in_main(unsigned int seconds)
{
boost::this_thread::sleep_for(boost::chrono::seconds(seconds));
gil_lock lock; // Acquire GIL.
display(); // Can safely modify python objects.
// GIL released when lock goes out of scope.
}
/// @brief Schedule the list to be displayed.
///
/// @param Delay in seconds.
void display_in(unsigned int seconds)
{
// Start detached thread.
boost::thread(&display_in_main, seconds).detach();
}
这是完整的示例:
#include <iostream> // std::cout
#include <utility> // std::make_pair
#include <boost/foreach.hpp>
#include <boost/python.hpp>
#include <boost/python/stl_iterator.hpp>
#include <boost/thread.hpp>
boost::python::list list;
/// @brief Store handle to the list.
///
/// @param pylist Python list for which a handle will be maintained.
void set(const boost::python::list& pylist)
{
list = pylist;
}
// Iterate over the current list, printing all ints.
void display()
{
std::cout << "in display" << std::endl;
typedef boost::python::stl_input_iterator<int> iterator_type;
BOOST_FOREACH(const iterator_type::value_type& data,
std::make_pair(iterator_type(list), // begin
iterator_type())) // end
{
std::cout << data << std::endl;
}
}
/// @brief RAII class used to lock and unlock the GIL.
class gil_lock
{
public:
gil_lock() { state_ = PyGILState_Ensure(); }
~gil_lock() { PyGILState_Release(state_); }
private:
PyGILState_STATE state_;
};
/// @brief Entry point for delayed display thread.
///
/// @param Delay in seconds.
void display_in_main(unsigned int seconds)
{
boost::this_thread::sleep_for(boost::chrono::seconds(seconds));
gil_lock lock; // Acquire GIL.
display(); // Can safely modify python objects.
// GIL released when lock goes out of scope.
}
/// @brief Schedule the list to be displayed.
///
/// @param Delay in seconds.
void display_in(unsigned int seconds)
{
// Start detached thread.
boost::thread(&display_in_main, seconds).detach();
}
BOOST_PYTHON_MODULE(example) {
PyEval_InitThreads(); // Initialize GIL to support non-python threads.
namespace python = boost::python;
python::def("set", &set);
python::def("display", &display);
python::def("display_in", &display_in);
}
及其用法:
>>> import example
>>> from time import sleep
>>>
>>> x = range(2)
>>> example.set(x)
>>> example.display()
in display
0
1
>>> example.display_in(3)
>>> x[:] = range(7, 10)
>>> print "sleeping"
sleeping
>>> sleep(6)
in display
7
8
9
虽然 Python 控制台阻塞了 6 秒sleep(6)
调用时,C++线程获取了GIL,显示了list的内容x
,并发布了GIL。