问题:参考了很多博主的帖子,想要实现多次调用python脚本都是对全局解释器锁GIL和线程的相关操作用类封装。会导致的程序崩溃问题,以及错误提示。
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);
//代码后方什么也不加
}
水平有限,希望大家海涵。欢迎关注,评论区交流。
平时不定期分享以下自己的小经验和心得。