在QThread中使用pybind11触发GIL错误导致应用崩溃
我实现了一个嵌入式Python模块,包含如下C++代码:
PYBIND11_EMBEDDED_MODULE(appmodule, m) { py::class_<Camera>(m, "Camera") .def(py::init<>()) .def("onFrameEvent", &Camera::onFrameEvent, pybind11::call_guard<pybind11::gil_scoped_release>()); }
根据文档说明,在多线程场景下我添加了pybind11::call_guard<pybind11::gil_scoped_release>()。
接下来定义了继承自QThread的Worker类:
class Worker : public QThread { Q_OBJECT public: Worker(Camera* cam); void run(); private: Camera* camera; };
Worker的run方法实现:
void Worker::run() { // 以下代码在其他线程中运行: QTimer* loop = new QTimer(); connect(loop, &QTimer::timeout, [=]() { cv::Mat m = camera->getOpenCVMat(); py::array_t<uint8_t> output( py::buffer_info( m.data, sizeof(uint8_t), py::format_descriptor<uint8_t>::format(), 3, std::vector<size_t> {static_cast<unsigned long>(m.rows), static_cast<unsigned long>(m.cols), 3}, std::vector<size_t> { sizeof(uint8_t) * static_cast<unsigned long>(m.cols) * 3, sizeof(uint8_t) * 3, sizeof(uint8_t)})); camera->getPythonHandle().attr("onFrameEvent")(output); // 调用Python侧的事件方法 }); loop->start(1000); QThread::exec(); }
Python侧代码:
import appmodule import cv2 class MyCamera(appmodule.Camera): def __init__(self): super().__init__() def onFrameEvent(self, image): cv2.imshow('', image)
我在main()函数开头用py::initialize_interpreter();初始化了解释器。
崩溃现象
应用崩溃,抛出错误:
pybind11::handle::inc_ref() is being called while the GIL is either
not held or invalid. Please see
https://pybind11.readthedocs.io/en/stable/advanced/misc.html#common-sources-of-global-interpreter-lock-errors
for debugging advice.If you are convinced there is no bug in your
code, you can #define PYBIND11_NO_ASSERT_GIL_HELD_INCREF_DECREFto
disable this check. In that case you have to ensure this #define is
consistently used for all translation units linked into a given
pybind11 extension, otherwise there will be ODR violations.The failing
pybind11::handle::inc_ref() call was triggered on a function object.pybind11::handle::dec_ref() is being called while the GIL is either
not held or invalid. Please see
https://pybind11.readthedocs.io/en/stable/advanced/misc.html#common-sources-of-global-interpreter-lock-errors
for debugging advice. If you are convinced there is no bug in your
code, you can #define PYBIND11_NO_ASSERT_GIL_HELD_INCREF_DECREFto
disable this check. In that case you have to ensure this #define is
consistently used for all translation units linked into a given
pybind11 extension, otherwise there will be ODR violations.The failing
pybind11::handle::dec_ref() call was triggered on a function object.libc++abi: terminating due to uncaught exception of type
std::runtime_error: pybind11::handle::dec_ref() PyGILState_Check()
failure.
定义PYBIND11_NO_ASSERT_GIL_HELD_INCREF_DECREF后依然崩溃,错误变为:
libc++abi: terminating due to uncaught exception of type std::runtime_error: pybind11::handle::dec_ref() PyGILState_Check() failure.
尝试在run()方法开头添加py::gil_scoped_release release;,问题未解决。
系统环境:MacOS Ventura
问题核心在于跨线程调用Python代码时没有正确获取GIL,同时你对call_guard<gil_scoped_release>的使用场景搞反了。
1. 修正call_guard的使用
pybind11::call_guard<gil_scoped_release>的作用是调用C++函数时自动释放GIL,适用于C++函数执行纯耗时操作、不需要调用Python API的场景。但你的Camera::onFrameEvent最终要触发Python侧的重写方法,这里不能释放GIL,反而需要确保调用时持有GIL。所以要移除这个call_guard:
PYBIND11_EMBEDDED_MODULE(appmodule, m) { py::class_<Camera>(m, "Camera") .def(py::init<>()) .def("onFrameEvent", &Camera::onFrameEvent); // 移除gil_scoped_release }
2. 跨线程调用Python前手动获取GIL
在Worker线程的定时器回调中,你直接调用了Python对象的方法,但当前线程没有持有GIL,这会触发pybind11的断言错误。必须在调用任何Python相关代码前获取GIL,调用完成后自动释放:
修改定时器回调部分:
connect(loop, &QTimer::timeout, [=]() { cv::Mat m = camera->getOpenCVMat(); py::array_t<uint8_t> output( py::buffer_info( m.data, sizeof(uint8_t), py::format_descriptor<uint8_t>::format(), 3, std::vector<size_t> {static_cast<unsigned long>(m.rows), static_cast<unsigned long>(m.cols), 3}, std::vector<size_t> { sizeof(uint8_t) * static_cast<unsigned long>(m.cols) * 3, sizeof(uint8_t) * 3, sizeof(uint8_t)})); // 手动获取GIL,确保调用Python代码时持有全局解释器锁 py::gil_scoped_acquire acquire; camera->getPythonHandle().attr("onFrameEvent")(output); // 作用域结束后,acquire对象会自动释放GIL });
3. 额外优化建议
- 如果
getOpenCVMat()是耗时操作,保持它在GIL获取之前执行,避免长时间占用GIL阻塞Python线程的执行 - 确保
camera->getPythonHandle()返回的pybind11对象在多线程访问时是安全的,或者始终在持有GIL的情况下访问它
内容的提问来源于stack exchange,提问作者MHDtA

