QT(c++) 线程 调用python问题

1、背景

  简单说一下需求,Qt开发的上位机界面程序,需要调用Python编写的算法跑一个结果返回到界面上显示。

2、度娘出一篇博客,按照步骤进行环境搭建和简单的代码测试

  环境搭建请参照如下博客地址:

    博客:① https://blog.csdn.net/cholenmine/article/details/82854301

       ② https://blog.csdn.net/yinyuchen1/article/details/77775851

 

#include "Python.h"
 
void MainWindow::test()
 
{
    //进行初始化
    Py_Initialize();
    //如果初始化失败,返回
    if(!Py_IsInitialized())
    {
        qDebug()<<"11111111111111111111";
        return ;
    }
 
    //加载模块,模块名称为myModule,就是myModule.py文件
    PyObject *pModule = PyImport_ImportModule("myModule");
    //如果加载失败,则返回
    if(!pModule)
    {
        qDebug()<<"2222222222222222";
        return;
    }
 
   //加载函数greatFunc
    PyObject * pFuncHello = PyObject_GetAttrString(pModule, "greatFunc");
 
    //如果失败则返回
    if(!pFuncHello)
    {
        qDebug()<<"3333333333333333333333";
        return ;Py_Finalize();      
}

 

3、根据目前的具体需求,我需要在开启一个线程来调用Python脚本,于是用qt内部的信号槽来使用线程调用,调用方法还是用的上面的示例代码。

  .h文件

void Widget::handleLoadGCode(QString str)
{
    m_loadGCodeClick = true;
	pQwait->SetShowText(u8"提示", u8"加载G代码中,请稍后");
	pQwait->show();
 
	if (!m_isInitPy)
		m_consuming->PythonInit();
	else
		qDebug() << "python环境已经初始化了";
 
	m_isInitPy = true;
	QThread *thread = new QThread;
 
	connect(thread, &QThread::started, [=]() {
		m_consuming->LoadGCode(str);
	});
 
	connect(m_consuming, SIGNAL(successed()), this, SLOT(handleTimeConsumingEnd()));
	connect(m_consuming, SIGNAL(successed()), thread, SLOT(quit()));
	connect(m_consuming, SIGNAL(failed()), this, SLOT(handleTimeConsumingFailed()));
	connect(m_consuming, SIGNAL(failed()), thread, SLOT(quit()));
	connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
	m_consuming->moveToThread(thread);
	thread->start();
 
}

线程类一定要是继承自qobject的,就行了。

现在问题来了:

  ① 第一次调用python脚本,能够正常调用并且得到结果。

  ② 不关闭主界面,接着进行第二次调用,软件直接崩溃,崩溃的行数是PyImport_ImportModule()函数。

 

最开始分析的原因:① 出现了空指针 

         ② 第二次调用时,第一次的资源没有释放,占用python脚本,导致PyImport_ImportModule()函数不能将模块导入

4、最后差资料发现,因为我这里使用的是线程,C++多线调用python时必须要控制GIL

 

参照如下博客的方法才得以解决这个问题,对于小白初次线程中调用Python,鬼知道要控制什么GIL,虽然问题解决了,到现在都没去看GIL是个什么鬼  

  https://blog.csdn.net/qq_42938320/article/details/101770269

 

解决方法 :

1:先新建一个.h文件:如下

//将全局解释器锁和线程的相关操作用类封装
#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

2:然后写个PythonInit函数,这个函数只调一次就行了。

void Robot_time_consuming::PythonInit()
{
	if (!Py_IsInitialized())
	{
		//1.初始化Python解释器,这是调用操作的第一步
		Py_Initialize();
		if (!Py_IsInitialized()) {
			qDebug("初始化Python解释器失败");
			emit failed();
		}
		else {
 
			//执行单句Python语句,用于给出调用模块的路径,否则将无法找到相应的调用模块
			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解释器成功");
		}
	}
 
}

3:然后再你调用python函数的地方这样写,记住,之前的

Py_Finalize(); 要删除

void Robot_time_consuming::LoadGCode(QString GName)
{
	class PyThreadStateLock PyThreadLock;//获取全局锁
 
	//导入TransferKRL.py模块
	PyObject* pModule = PyImport_ImportModule("TransferKRL");
	if (!pModule) {
		QString infoData = "Can not open python file!";
		qDebug() << infoData;
		emit failed();
		return;
	}
 
 
	PyObject *pyClass = PyObject_GetAttrString(pModule, "GcodeToTrack");
	PyObject *pConstruct = PyInstanceMethod_New(pyClass);
 
 
	PyObject* pParams = PyTuple_New(2);
	PyTuple_SetItem(pParams, 0, Py_BuildValue("s", GName.toStdString().c_str()));
	PyTuple_SetItem(pParams, 1, Py_BuildValue("s", "trackFile01.csv"));
	PyObject* pIns = PyObject_CallObject(pConstruct, pParams);
 
 
 
	int res = 0;
	// PyArg_Parse(FuncTwoBack,"i",&res);//转换返回类型
	qDebug() << "res:" << res;
	emit successed();
}

 

 

参考博文:QT多线程调用python

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

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Windows 下使用 Qt C++ 调用 Python 有多种方法,以下是其中一种比较简单的方法: 1. 安装 Python 和 PyQt 首先,在 Windows 下安装 Python 和 PyQt。 2. 创建 Qt 项目 使用 Qt Creator 创建一个 Qt 项目,选择 C++ 应用程序。 3. 添加 Python 支持 在项目的 .pro 文件中添加以下内容: ``` CONFIG += link_pkgconfig PKGCONFIG += python-3.6 LIBS += -LC:/Python36/libs -lpython36 INCLUDEPATH += C:/Python36/include DEPENDPATH += C:/Python36/include ``` 其中,python-3.6 是你安装的 Python 版本号,C:/Python36 是 Python 的安装路径。 4. 创建 Python 脚本 在项目中创建一个 Python 脚本,例如 test.py,内容如下: ``` def add(a, b): return a + b ``` 5. 在 C++ 中调用 Python 在 C++ 中调用 Python 可以使用 Python.h 头文件和 Python 的 API。以下是一个简单的示例: ```cpp #include <Python.h> int main(int argc, char *argv[]) { Py_Initialize(); PyObject *pModule = PyImport_ImportModule("test"); if (pModule) { PyObject *pFunc = PyObject_GetAttrString(pModule, "add"); if (pFunc && PyCallable_Check(pFunc)) { PyObject *pArgs = PyTuple_New(2); PyTuple_SetItem(pArgs, 0, PyLong_FromLong(1)); PyTuple_SetItem(pArgs, 1, PyLong_FromLong(2)); PyObject *pResult = PyObject_CallObject(pFunc, pArgs); if (pResult) { long result = PyLong_AsLong(pResult); printf("result=%ld\n", result); Py_DECREF(pResult); } Py_DECREF(pArgs); } Py_DECREF(pFunc); } Py_DECREF(pModule); Py_Finalize(); return 0; } ``` 以上示例代码调用Python 脚本中的 add 方法,传入两个参数 1 和 2,输出结果 3。 注意事项: - 在调用Python API 后需要释放对象,避免内存泄漏。 - 实际项目中,可能需要使用 PyGILState_Ensure 和 PyGILState_Release 来保证线程安全。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值