为什么PyGILState_Release会引发致命的Python错误
已解决
好的,我解决了这个问题。关键在于你如何初始化线程状态。你根本不需要使用ReleaseLock。只需在你的模块定义中添加InitThreads调用:
BOOST_PYTHON_MODULE(ModuleName)
{
PyEval_InitThreads();
...
}
我花了好几个小时来诊断这个问题,查阅了网上几乎所有的例子。现在有点累了,可能错过了一些明显的东西,但事情是这样的:
我正在用boost python封装一个库。我运行一个python脚本,导入这个库,构造一些对象,然后接收来自C++的回调,这些回调又会调用python。在我调用任何python函数之前,我尝试获取全局解释器锁。以下是一些示例代码:
class ScopedGILRelease
{
public:
inline ScopedGILRelease()
{
d_gstate = PyGILState_Ensure();
}
inline ~ScopedGILRelease()
{
PyGILState_Release(d_gstate);
}
private:
PyGILState_STATE d_gstate;
};
class PyTarget : public DingoClient::ClientRequest::Target, public wrapper<DingoClient::ClientRequest::Target>
{
public:
PyTarget(PyObject* self_) : self(self_) {}
~PyTarget() {
ScopedGILRelease gil_lock;
}
PyObject* self;
void onData(const boost::shared_ptr<Datum>::P & data, const void * closure)
{
ScopedGILRelease gil_lock;
// invoke call_method to python
}
...
}
Target对象上的onData方法是由库作为回调调用的。在python中,我们从PyTarget继承并实现另一个方法。然后我们使用call_method<>来调用这个方法。gil_lock获取锁,并通过RAII确保获取的线程状态总是被释放,并且在超出作用域时确实总是被释放。
然而,当我在一个脚本中运行这个,试图在这个函数上获得大量回调时,它总是会崩溃。脚本大致如下:
# Initialize the library and setup callbacks
...
# Wait until user breaks
while 1:
pass
此外,python脚本总是构造一个对象,该对象运行:
PyEval_InitThreads();
PyEval_ReleaseLock();
在接收任何回调之前。
我把代码简化到连onData中都不调用python,只是获取锁。在释放时,它总是崩溃,出现以下情况:
Fatal Python error: ceval: tstate mix-up
Fatal Python error: This thread state must be current when releasing
或者
Fatal Python error: ceval: orphan tstate
Fatal Python error: This thread state must be current when releasing
看起来是随机的。我是不是疯了,因为我觉得我正确地使用了GIL锁,但它似乎根本不起作用。
其他说明: 只有一个线程会调用那个Target对象的onData方法。
当我在调用的python模块的while循环中使用time.sleep()时,它似乎允许脚本运行得更久,但最终脚本还是会崩溃,出现类似的问题。它持续的时间与time.sleep的时间成正比(例如,time.sleep(10)运行得比time.sleep(0.01)更久)。这让我觉得脚本可能在未经我允许的情况下重新获取了GIL。
在我的代码中没有其他地方调用PyGILState_Release和PyGILState_Ensure,也没有其他地方应该调用python。
更新
我读到另一个问题,建议在模块中导入threading,作为运行的替代方案:
PyEval_InitThreads();
PyEval_ReleaseLock();
然而,当我在我的模块之前导入threading并删除boost python包装中的上述两行时,它似乎不起作用。
1 个回答
好的,我解决了这个问题。关键在于你是如何初始化线程状态的。其实你根本不需要使用ReleaseLock。只需要在你的模块定义中添加InitThreads调用就可以了:
BOOST_PYTHON_MODULE(ModuleName)
{
PyEval_InitThreads();
...
}