You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 22:54:53