Python、线程、GIL 与 C++
有没有办法让boost::python在每次与Python交互时都控制Python的全局解释器锁(GIL)?
我正在用boost::python写一个项目。我想为一个外部库写一个C++的封装,并用Python脚本来控制这个C++库。我不能修改外部库,只能修改我的封装程序。(我在为这个外部库写一个功能测试应用)
这个外部库是用C语言写的,使用函数指针和回调来处理很多复杂的操作。它是一个消息系统,所以当有消息到达时,就会调用一个回调函数。
我在我的库中实现了观察者模式,这样多个对象可以监听同一个回调。我已经把主要的部分导出得很好,能够在一定程度上很好地控制一切。
这个外部库会创建线程来处理消息、发送消息、进行处理等等。有些回调可能会从不同的进程中被调用,而我最近发现Python并不是线程安全的。
这些观察者可以在Python中定义,所以我需要能够随时调用Python,而Python也需要能够调用我的程序。
我这样设置对象和观察者:
class TestObserver( MyLib.ConnectionObserver ):
def receivedMsg( self, msg ):
print("Received a message!")
ob = TestObserver()
cnx = MyLib.Conection()
cnx.attachObserver( ob )
然后我创建一个源来发送到连接,接收到的消息函数就会被调用。
所以一个普通的source.send('msg')会进入我的C++应用,传递到C库,然后发送消息,连接会接收到它,然后调用回调,这个回调又回到我的C++库,连接尝试通知所有观察者,此时的观察者就是这里的Python类,因此它会调用那个方法。
当然,回调是从连接线程中调用的,而不是主应用线程。
昨天一切都崩溃了,我连发送一条消息都做不到。然后在Cplusplus-sig的档案中翻找后,我了解到GIL和一些锁定的函数。
所以我现在的C++ Python封装类看起来是这样的:
struct IConnectionObserver_wrapper : Observers::IConnectionObserver, wrapper<Observers::IConnectionObserver>
{
void receivedMsg( const Message* msg )
{
PyGILState_STATE gstate = PyGILState_Ensure();
if( override receivedMsg_func = this->get_override( "receivedMsg" ) )
receivedMsg_func( msg );
Observers::IConnectionObserver::receivedMsg( msg );
PyGILState_Release( gstate );
}
}
这有效,但当我尝试发送超过250条消息时:
for i in range(250)
source.send('msg")
它又崩溃了。出现的错误和之前一样,
PyThreadState_Get: no current thread
所以我在想这次我可能是调用我的C++应用时出了问题,而不是调用Python。
我的问题是,有没有办法让boost::python自己处理每次与Python交互时的GIL?我在代码中找不到任何相关的内容,而且很难找到source.send调用是如何进入boost_python的 :(
3 个回答
我觉得最好的办法是避免使用全局解释器锁(GIL),确保你和Python的交互是单线程的。
我现在正在设计一个基于boost.python的测试工具,我想我可能会使用一个生产者/消费者队列来处理来自多线程库的事件,这些事件会被Python线程按顺序读取。
我不太清楚你的具体问题,但你可以看看“调用策略”(CallPolicies):
http://www.boost.org/doc/libs/1_37_0/libs/python/doc/v2/CallPolicies.html#CallPolicies-concept
你可以定义新的调用策略(比如有一种叫“返回内部引用”的策略),它可以在执行被包装的C++函数之前和/或者之后运行一些代码。我成功地实现了一种调用策略,可以在执行C++包装的函数之前自动释放全局解释器锁(GIL),然后在返回Python之前再重新获取它,这样我就可以写出这样的代码:
.def( "long_operation", &long_operation, release_gil<>() );
使用调用策略可能会让你更容易地编写这段代码。
我在一个邮件列表上发现了一个很冷门的帖子,里面提到在 BOOST_PYTHON_MODULE 中使用 PyEval_InitThreads();
,结果这真的似乎解决了崩溃的问题。
不过,程序是否能报告它收到的所有消息还是个未知数。如果我发送2000条消息,大多数情况下它会说收到了2000条,但有时候报告的数量会少很多。
我怀疑这可能是因为多个线程同时访问我的计数器,所以我在这里回答这个问题,因为这属于另一个问题。
要解决这个问题,只需执行
BOOST_PYTHON_MODULE(MyLib)
{
PyEval_InitThreads();
class_ stuff