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();
}
  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搁浅的渔

创作不易,多多支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值