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

1、背景

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

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

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

    博客:① https://blog.youkuaiyun.com/cholenmine/article/details/82854301

       ② https://blog.youkuaiyun.com/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.youkuaiyun.com/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();
}
### C++线程调用 Python 程序的方法 #### 初始化 Python 解释器并设置环境 为了使 C++ 可以成功调用 Python 类,在启动任何操作之前,必须初始化 Python 的解释器。这一步骤确保了后续所有的 Python API 函数能够正常工作。 ```cpp #include <Python.h> // ...其他必要的头文件... int main() { Py_Initialize(); // 初始化Python解释器 } ``` #### 创建新线程执行 Python 代码 当希望在一个独立的线程中运行 Python 代码时,可以创建一个新的标准库线程,并在线程函数内加载所需的 Python 文件或直接执行字符串形式的 Python 命令: ```cpp void run_python_script_in_thread(const char* script_name) { PyObject *pName, *pModule; pName = PyUnicode_DecodeFSDefault(script_name); pModule = PyImport_Import(pName); if (pModule != nullptr){ // 成功导入模块后的处理逻辑... } else { PyErr_Print(); } Py_XDECREF(pName); Py_XDECREF(pModule); } std::thread t(run_python_script_in_thread, "example.py"); t.join(); ``` #### 使用 `PyGILState_Ensure` 和 `PyGILState_Release` 由于 Python 的全局解释锁(Global Interpreter Lock, GIL),在同一时刻只有一个线程可以在CPython 中真正地执行字节码。因此,在多线程环境中调用 Python 接口前,应当先获取 GIL;完成之后再释放它[^1]。 ```cpp void thread_function(){ PyGILState_STATE gstate; gstate = PyGILState_Ensure(); /* 执行一些Python相关的操作 */ // ... PyGILState_Release(gstate); } ``` #### 实现回调机制 如果想要让 C++ 线程中的某些事件触发 Python 函数,则可以通过传递给定的对象作为参数的方式实现回调功能。这里展示了一个简单的例子,其中定义了一个名为 `callback` 的 Python 对象,并通过 `PyEval_CallObject` 来调用这个对象所代表的可调用实体[^2]。 ```cpp PyObject* callback; // 这里假设已经设置了有效的回调对象 /* 当某个条件满足时 */ if(/* condition */){ PyObject_CallObject(callback, NULL); // 调用Python回调函数 } ``` #### 结合 Qt 框架下的应用案例 对于基于Qt的应用程序来说,可能更倾向于利用其内置的支持来进行跨语言交互。在这种情况下,除了上述提到的技术外,还可以考虑使用信号槽机制来简化不同组件间的通信过程。例如,可以在主线程之外建立专门的工作线程用于处理来自 Python 的请求,并借助于这些特性同步两者之间的状态变化[^3]。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

搁浅的渔

创作不易,多多支持

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

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

打赏作者

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

抵扣说明:

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

余额充值