<P>如果从一个不显式管理<a href="http://wiki.python.org/moin/GlobalInterpreterLock" rel="noreferrer">Global Interpreter Lock</a>(吉尔)的C++线程调用^ {CD1>},则会导致未定义的行为。在</p>
<hr/>
< H2> Python和C++线程。</H2>
<P>让我们考虑C++线程与Python交互的情况。例如,可以设置一个C++线程,通过^ {CD3}}在一段时间后调用^ {< CD2> }的信号。在</p>
<p>这个例子可能会涉及很多,所以让我们从基础知识开始:Python的GIL。简而言之,GIL是围绕着翻译的互斥体。如果一个线程正在做任何影响python托管对象的引用计数的操作,那么它需要获得GIL。在GDB回溯中增压信号2library可能试图在没有GIL的情况下调用Python对象,导致崩溃。虽然管理GIL相当简单,但它很快就会变得复杂。在</p>
<p>首先,该模块需要Python初始化GIL进行线程化。在</p>
<pre class="lang-cpp prettyprint-override"><code>BOOST_PYTHON_MODULE(example)
{
PyEval_InitThreads(); // Initialize GIL to support non-python threads.
// ...
}
</code></pre>
<p>为了方便起见,让我们创建一个简单的类来帮助通过作用域管理GIL:</p>
^{pr2}$
<P>让我们确认C++线程何时需要吉尔:</p>
<ul>
<li><code>boost::signals2::signal</code>可以创建连接对象的附加副本,就像调用信号时一样。在</li>
<li>调用通过<code>boost::signals2::signal</code>连接的Python对象。回调肯定会影响python对象。例如,提供给<code>__call__</code>方法的<code>self</code>参数将增加或减少对象的引用计数。在</li>
</ul>
<h3><code>MyClass</code>类。</h3>
<p>以下是基于原始代码的基本模型类:</p>
<pre class="lang-cpp prettyprint-override"><code>/// @brief Mockup class.
class MyClass
{
public:
/// @brief Connect a slot to the signal.
template <typename Slot>
void connect_slot(const Slot& slot)
{
signal_.connect(slot);
}
/// @brief Send an event to the signal.
void event(int value)
{
signal_(value);
}
private:
boost::signals2::signal<void(int)> signal_;
};
</code></pre>
<P>作为C++线程,可能调用^ {CD2>}的信号,其生存期至少为线程的长度。一个很好的人选是Boost.Python使用<code>boost::shared_ptr</code>管理<code>MyClass</code>。在</p>
<pre class="lang-cpp prettyprint-override"><code>BOOST_PYTHON_MODULE(example)
{
PyEval_InitThreads(); // Initialize GIL to support non-python threads.
namespace python = boost::python;
python::class_<MyClass, boost::shared_ptr<MyClass>,
boost::noncopyable>("MyClass")
.def("event", &MyClass::event)
// ...
;
}
</code></pre>
<h3><code>boost::signals2::signal</code>与python对象交互。</h3>
<p><code>boost::signals2::signal</code>在调用时可能会生成副本。另外,可能有连接到信号的C++插槽,所以在调用Python插槽时只锁定吉尔是理想的。但是,<code>signal</code>并没有提供钩子来允许我们在创建slot副本或调用slot之前获取GIL。在</p>
<p>为了避免<code>signal</code>创建<code>boost::python::object</code>槽的副本,可以使用一个包装类来创建<code>boost::python::object</code>的副本,以便引用计数保持准确,并通过<code>shared_ptr</code>管理副本。这允许<code>signal</code>自由地创建<code>shared_ptr</code>的副本,而不是在没有GIL的情况下复制{<cd17>}。在</p>
<p>这个GIL安全槽可以封装在helper类中。在</p>
<pre class="lang-cpp prettyprint-override"><code>/// @brief Helepr type that will manage the GIL for a python slot.
///
/// @detail GIL management:
/// * Caller must own GIL when constructing py_slot, as
/// the python::object will be copy-constructed (increment
/// reference to the 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_slot is invoked (operator()), it will acquire
/// the GIL then delegate to the managed python::object.
struct py_slot
{
public:
/// @brief Constructor that assumes the caller has the GIL locked.
py_slot(const boost::python::object& object)
: object_(
new boost::python::object(object), // GIL locked, so copy.
[](boost::python::object* object) // Delete needs GIL.
{
gil_lock lock;
delete object;
}
)
{}
// Use default copy-constructor and assignment-operator.
py_slot(const py_slot&) = default;
py_slot& operator=(const py_slot&) = default;
template <typename ...Args>
void operator()(Args... args)
{
// Lock the GIL as the python object is going to be invoked.
gil_lock lock;
(*object_)(args...);
}
private:
boost::shared_ptr<boost::python::object> object_;
};
</code></pre>
<p>一个helper函数将向Python公开,以帮助调整类型。在</p>
<pre class="lang-cpp prettyprint-override"><code>/// @brief MyClass::connect_slot helper.
template <typename ...Args>
void MyClass_connect_slot(
MyClass& self,
boost::python::object object)
{
py_slot slot(object); // Adapt object to a py_slot for GIL management.
// Using a lambda here allows for the args to be expanded automatically.
// If bind was used, the placeholders would need to be explicitly added.
self.connect_slot([slot](Args... args) mutable { slot(args...); });
}
</code></pre>
<p>更新后的绑定公开了helper函数:</p>
<pre class="lang-cpp prettyprint-override"><code>python::class_<MyClass, boost::shared_ptr<MyClass>,
boost::noncopyable>("MyClass")
.def("connect_slot", &MyClass_connect_slot<int>)
.def("event", &MyClass::event)
// ...
;
</code></pre>
<h3>线本身。</h3>
<p>线程的功能相当基本:它休眠然后调用信号。然而,理解GIL的背景是很重要的。在</p>
<pre class="lang-cpp prettyprint-override"><code>/// @brief Sleep then invoke an event on MyClass.
template <typename ...Args>
void MyClass_event_in_thread(
boost::shared_ptr<MyClass> self,
unsigned int seconds,
Args... args)
{
// Sleep without the GIl.
std::this_thread::sleep_for(std::chrono::seconds(seconds));
// We do not want to hold the GIL while invoking or copying
// C++-specific slots connected to the signal. Thus, it is the
// responsibility of python slots to manage the GIL via the
// py_slot wrapper class.
self->event(args...);
}
/// @brief Function that will be exposed to python that will create
/// a thread to call the signal.
template <typename ...Args>
void MyClass_event_in(
boost::shared_ptr<MyClass> self,
unsigned int seconds,
Args... args)
{
// The caller may or may not have the GIL. Regardless, spawn off a
// thread that will sleep and then invoke an event on MyClass. The
// thread will not be joined so detach from it. Additionally, as
// shared_ptr is thread safe, copies of it can be made without the
// GIL.
std::thread(&MyClass_event_in_thread<Args...>, self, seconds, args...)
.detach();
}
</code></pre>
<p><sub>注意,<code>MyClass_event_in_thread</code>可以表示为lambda,但是在lambda中解压模板包在某些编译器上不起作用。</sub></p>
<p>并更新<code>MyClass</code>绑定。在</p>
<pre class="lang-cpp prettyprint-override"><code>python::class_<MyClass, boost::shared_ptr<MyClass>,
boost::noncopyable>("MyClass")
.def("connect_slot", &MyClass_connect_slot<int>)
.def("event", &MyClass::event)
.def("event_in", &MyClass_event_in<int>)
;
</code></pre>
<hr/>
<p>最终解决方案如下:</p>
<pre class="lang-cpp prettyprint-override"><code>#include <thread> // std::thread, std::chrono
#include <boost/python.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/signals2/signal.hpp>
/// @brief Mockup class.
class MyClass
{
public:
/// @brief Connect a slot to the signal.
template <typename Slot>
void connect_slot(const Slot& slot)
{
signal_.connect(slot);
}
/// @brief Send an event to the signal.
void event(int value)
{
signal_(value);
}
private:
boost::signals2::signal<void(int)> signal_;
};
/// @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 Helepr type that will manage the GIL for a python slot.
///
/// @detail GIL management:
/// * Caller must own GIL when constructing py_slot, as
/// the python::object will be copy-constructed (increment
/// reference to the 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_slot is invoked (operator()), it will acquire
/// the GIL then delegate to the managed python::object.
struct py_slot
{
public:
/// @brief Constructor that assumes the caller has the GIL locked.
py_slot(const boost::python::object& object)
: object_(
new boost::python::object(object), // GIL locked, so copy.
[](boost::python::object* object) // Delete needs GIL.
{
gil_lock lock;
delete object;
}
)
{}
// Use default copy-constructor and assignment-operator.
py_slot(const py_slot&) = default;
py_slot& operator=(const py_slot&) = default;
template <typename ...Args>
void operator()(Args... args)
{
// Lock the GIL as the python object is going to be invoked.
gil_lock lock;
(*object_)(args...);
}
private:
boost::shared_ptr<boost::python::object> object_;
};
/// @brief MyClass::connect_slot helper.
template <typename ...Args>
void MyClass_connect_slot(
MyClass& self,
boost::python::object object)
{
py_slot slot(object); // Adapt object to a py_slot for GIL management.
// Using a lambda here allows for the args to be expanded automatically.
// If bind was used, the placeholders would need to be explicitly added.
self.connect_slot([slot](Args... args) mutable { slot(args...); });
}
/// @brief Sleep then invoke an event on MyClass.
template <typename ...Args>
void MyClass_event_in_thread(
boost::shared_ptr<MyClass> self,
unsigned int seconds,
Args... args)
{
// Sleep without the GIL.
std::this_thread::sleep_for(std::chrono::seconds(seconds));
// We do not want to hold the GIL while invoking or copying
// C++-specific slots connected to the signal. Thus, it is the
// responsibility of python slots to manage the GIL via the
// py_slot wrapper class.
self->event(args...);
}
/// @brief Function that will be exposed to python that will create
/// a thread to call the signal.
template <typename ...Args>
void MyClass_event_in(
boost::shared_ptr<MyClass> self,
unsigned int seconds,
Args... args)
{
// The caller may or may not have the GIL. Regardless, spawn off a
// thread that will sleep and then invoke an event on MyClass. The
// thread will not be joined so detach from it. Additionally, as
// shared_ptr is thread safe, copies of it can be made without the
// GIL.
// Note: MyClass_event_in_thread could be expressed as a lambda,
// but unpacking a template pack within a lambda does not work
// on some compilers.
std::thread(&MyClass_event_in_thread<Args...>, self, seconds, args...)
.detach();
}
BOOST_PYTHON_MODULE(example)
{
PyEval_InitThreads(); // Initialize GIL to support non-python threads.
namespace python = boost::python;
python::class_<MyClass, boost::shared_ptr<MyClass>,
boost::noncopyable>("MyClass")
.def("connect_slot", &MyClass_connect_slot<int>)
.def("event", &MyClass::event)
.def("event_in", &MyClass_event_in<int>)
;
}
</code></pre>
<p>以及测试脚本:</p>
<pre class="lang-python prettyprint-override"><code>from time import sleep
import example
def spam1(x):
print "spam1: ", x
def spam2(x):
print "spam2: ", x
c = example.MyClass()
c.connect_slot(spam1)
c.connect_slot(spam2)
c.event(123)
print "Sleeping"
c.event_in(3, 321)
sleep(5)
print "Done sleeping"
</code></pre>
<p>结果如下:</p>
<pre class="lang-none prettyprint-override"><code>spam1: 123
spam2: 123
Sleeping
spam1: 321
spam2: 321
Done sleeping
</code></pre>