[已解决-最实用]Qt多次(多线程)调用python代码只能运行一次,调用全局解释锁GIL时造成的Fatal Python error: PyEval_SaveThread: NULL 等问题。

问题:参考了很多博主的帖子,想要实现多次调用python脚本都是对全局解释器锁GIL和线程的相关操作用类封装。会导致的程序崩溃问题,以及错误提示。

QT多线程调用python-CSDN博客

QT多次调用python脚本_qt多线程调用python-CSDN博客

总结一下要点:

一、要将初始化python解释器单独提取出来在一个函数内部定义。

void MainWindow::InitPy()
{
    Py_Initialize();
    if(!Py_IsInitialized())
    {
        qDebug("Initial Python failed!");
    }
    else
    {
        PyRun_SimpleString("import sys");
        QString setSysPath = QString("sys.path.append('%1')").arg(QCoreApplication::applicationDirPath());
                    PyRun_SimpleString(setSysPath.toStdString().c_str());
    //    PyEval_InitThreads();
        // 启动子线程前执行,为了释放PyEval_InitThreads获得的全局锁,否则子线程可能无法获取到全局锁。
        PyEval_ReleaseThread(PyThreadState_Get());
        qDebug("初始化Python解释器成功");

        InitPyFlag = 1;
    }

}
————————————————
版权声明:本文为CSDN博主「凌汐凡」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_46181090/article/details/126300192

注意,这里有三个大坑得注意:

第一坑: Py_Initialize();这句代码前一句一定要加上你的python安装路径,否则程序找不到python解释器,会报错GDB中断,例如应该为:

    Py_SetPythonHome(L"D:/Program Files/Python37");
    Py_Initialize();

第二坑:Py_Initialize();初始化解释器时在你的代码中只能被调用一次,因此避免除了初始化函数别的地方也出现。

第三坑:初始化函数InitPy()最好是在构造函数中随程序生成之初就将python解释器初始化。例如,同理对于解释器释放Py_Finalize();则定义在析构函数里,关于这个函数有个大坑,我后面再提:

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    InitPy();
}

MainWindow::~MainWindow()
{
    delete ui;
    Py_Finalize();
}

二、关于GIL全局解释锁的调用,参考以上提到的博主的代码:

#ifndef PYTHREADSTATELOCK_H
#define PYTHREADSTATELOCK_H
#include <Python.h>
class PyThreadStateLock
{
public:
    PyThreadStateLock(void)
    {
        _save = nullptr;
        nStatus = 0;
        nStatus = PyGILState_Check();   //检测当前线程是否拥有GIL
        PyGILState_STATE gstate;
        if (!nStatus)
        {
            gstate = PyGILState_Ensure();   //如果没有GIL,则申请获取GIL
            nStatus = 1;
        }
        _save = PyEval_SaveThread();
        PyEval_RestoreThread(_save);
    }
    ~PyThreadStateLock(void)
    {
        _save = PyEval_SaveThread();
        PyEval_RestoreThread(_save);
        if (nStatus)
        {
            PyGILState_Release(gstate);    //释放当前线程的GIL
        }
    }

private:
    PyGILState_STATE gstate;
    PyThreadState *_save;
    int nStatus;

};

#endif // PYTHREADSTATELOCK_H

注意,以上代码中会有两个地方提示错误,第一个是下面这句,是多的,应该删除:

PyGILState_STATE gstate;

第二个是可能会有Qt与Python槽函数冲突 ,因此在引用Python.h时应该注意进行处理,处理方法如下:

#undef slots
#include <Python.h>
#define slots Q_SLOTS

总结来说,我用的代码如下:

#ifndef PYTHREADSTATELOCK_H
#define PYTHREADSTATELOCK_H

#undef slots
#include <Python.h>
#define slots Q_SLOTS

class PyThreadStateLock
{
public:
    PyThreadStateLock(void)
    {
        _save = nullptr;
        nStatus = 0;
        nStatus = PyGILState_Check();   //检测当前线程是否拥有GIL
        if (!nStatus)
        {
            gstate = PyGILState_Ensure();   //如果没有GIL,则申请获取GIL
            nStatus = 1;
        }
        _save = PyEval_SaveThread();
        PyEval_RestoreThread(_save);
    }
    ~PyThreadStateLock(void)
    {
        _save = PyEval_SaveThread();
        PyEval_RestoreThread(_save);
        if (nStatus)
        {
            PyGILState_Release(gstate);    //释放当前线程的GIL
        }
    }

private:
    PyGILState_STATE gstate;
    PyThreadState *_save;
    int nStatus;

};

#endif // PYTHREADSTATELOCK_H

注意以上代码中除了python关键字之外的其他变量名均可以自己定义。但是一定要保存类名和构造函数的名一样,这里Qt中会提示。

三、接下来就是最重要的了。就是在多次调用Python脚本会遇到程序崩溃的问题。

第一个问题是,在多次调用python的代码所在.cpp头部一定要有文件的引用:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "pythreadstatelock.h"

不然会报错。

第二个问题是,Fatal Python error: PyEval_SaveThread: NULL的报错问题,这个问题的解决方法是:一定不要在代码结束末尾加上:

Py_CLEAR(pModule)
Py_DECREF(pModule)

这样的代码,我也没办法解释,估计是加上以后提前将 PyObject这些对象销毁后线程中仍然有阻塞。

另外就是,也不要在引用python代码末尾加上:

Py_Finalize();

这句代码,会提前导致解释器关闭,此时你再第二次进行操作的时候解释器已经关闭了,这时显然会报错,因此,我的代码是:

void MainWindow::RunPY()
{

    class PyThreadStateLock PyThreadLock;//获取全局锁
    pModule = PyImport_ImportModule("mainpy");
    if (!pModule)
     {
        PyErr_Print();
        std::exit(1);
         qDebug()<<"Cant open python file!\n";
     }
    qDebug()<<"file of python ac been opened";
    PyObject* pFunhello= PyObject_GetAttrString(pModule,"main");
    if(!pFunhello){
        qDebug()<<"Get function hello failed";
    }
    qDebug()<<"file success";
    PyObject_CallFunction(pFunhello,NULL);
//代码后方什么也不加
}

水平有限,希望大家海涵。欢迎关注,评论区交流。 

平时不定期分享以下自己的小经验和心得。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值