VS+QT多线程实现——run和moveToThread

写在前头:最近在学习多线程,以小白的视角写一些学习心得。也欢迎各位朋友勘误补充。
开发环境:VS2013+QT5

实现方法及特性

  • run ——继承QThread的run函数,通过重写run()方法,实现任务功能。
  1. 使用run方便理解,简单的任务流程可以封装在run里面。
  2. run 是线程的入口,run的开始和结束意味着线程的开始和结束。
  3. 多线程访问变量或处理事务要考虑加锁。(目前还未涉及加锁,不展开讨论)
  4. 线程start()起来,理论上run函数会从头执行到结束。所以如果run中的任务是多步骤,中途需要数据反馈或ui变化,做异常处理相对麻烦。
  5. QThread只有run函数是在新线程里的,其他所有函数都在QThread生成的线程里!!!
  • moveToThread——用moveToThread将继承于QObject的类转移到Thread里。
  1. 通过信号与槽连接,几乎不用考虑多线程的存在,也不用考虑使用QMutex来进行同步。
  2. QT4.8之后,QT官方建议使用此方法。

多线程run的实现

1.代码

myThread.h文件

#pragma once
#include <QThread>

class myThread :public QThread
{
	Q_OBJECT
public:
	myThread();
	~myThread();
private:
	void run();
};

myThread.CPP文件

#include "myThread.h"
#include <QDebug>

myThread::myThread()
{
}
myThread::~myThread()
{
}
//重写run函数
void myThread::run()
{
	qDebug() << "currentThreadId is" << QThread::currentThreadId();
	//Todo此处重写功能,此处用了for循环代替。可以根据需求定义功能
	for (int i = 0; i < 100;i++)
	{
		;
	}
	qDebug() << "currentThreadId " << QThread::currentThreadId()<<"run to end";
}

RunDemo.h文件

#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_RunDemo.h"
#include "myThread.h"
#include <QtDebug>

class RunDemo : public QMainWindow
{
	Q_OBJECT
public:
	RunDemo(QWidget *parent = Q_NULLPTR);
	~RunDemo();
	
private slots:
	void on_btnOK_Clicked();

private:
	Ui::RunDemoClass ui;
	myThread *m_thread;
};

RunDemo.cpp文件

#include "RunDemo.h"

RunDemo::RunDemo(QWidget *parent): QMainWindow(parent)
{
	ui.setupUi(this);
	//实例化
	m_thread = new myThread;
	//可以在QT Designer中连接。
	connect(ui.btnOK, SIGNAL(clicked()), this, SLOT(on_btnOK_Clicked()));
}

RunDemo::~RunDemo()
{
	if (m_thread)
	{
		delete m_thread;
		m_thread = NULL;
	}
}

void RunDemo::on_btnOK_Clicked()
{
	qDebug() << "MainThreadId is" << QThread::currentThreadId();
	m_thread->start();
	ui.lineEdit->setText("hello world");
}

2.效果

子线程里做了循环操作,没有执行任何任务。lineEdit文字显示为按键槽函数执行的结果。
在这里插入图片描述

moveToThread代码实现

1.代码

Woker.h文件

#pragma once
#include "qobject.h"
class Woker :public QObject
{
	Q_OBJECT
public:
	Woker();
	~Woker();

public slots:
    void doWork(int i);
signals:
	void SendToMain(int i);
};

Woker.cpp文件

#include "Woker.h"
#include <QDebug>
#include <QThread>

Woker::Woker()
{
}
Woker::~Woker()
{
}
//线程需要执行的任务
void Woker::doWork(int i)
{
	qDebug() << "currentThreadId is" << QThread::currentThreadId();
	//Todo此处为执行的任务功能,用了累加(+1)代替,可按需定义自己的功能
	i++;
	//发送信号
	emit SendToMain(i);
	qDebug() << "currentThreadId " << QThread::currentThreadId() << "run to end";
}

moveToThreadDemo.h文件

#pragma once

#include <QtWidgets/QMainWindow>
#include "ui_moveToThreadDemo.h"
#include <QThread>
#include "Woker.h"
#include <QString>
#include <QDebug>

class moveToThreadDemo : public QMainWindow
{
	Q_OBJECT

public:
	moveToThreadDemo(QWidget *parent = Q_NULLPTR);

private:
	Ui::moveToThreadDemoClass ui;

signals:
	//为方便理解,这里传递的参数为int数字,可以根据需求自定义类型
	//传递的参数也可以是结构体、类对象,项目中我传递了串口(指针)
	void SendToWoker(int i);
public slots:
    void on_btnOK_Clicked();
	void handleResult(int i);
};

moveToThreadDemo.cpp文件

#include "moveToThreadDemo.h"

moveToThreadDemo::moveToThreadDemo(QWidget *parent)
	: QMainWindow(parent)
{
	ui.setupUi(this);
	//按键功能连接
	connect(ui.btnOK, SIGNAL(clicked()), this, SLOT(on_btnOK_Clicked()));
	//声明任务woker
	Woker *myWoker = new Woker;
	//建立线程
	QThread *mythread = new QThread;
	
	connect(this, SIGNAL(SendToWoker(int)), myWoker, SLOT(doWork(int)));
	connect(myWoker, SIGNAL(SendToMain(int)), this, SLOT(handleResult(int)));
	connect(mythread, &QThread::finished, myWoker, &QObject::deleteLater);
	myWoker->moveToThread(mythread);
	mythread->start();

}
void moveToThreadDemo::on_btnOK_Clicked()
{
	qDebug() << "MainThreadId is" << QThread::currentThreadId();
	//将数字1作为信号传递出去
	emit SendToWoker(1);
}
//处理函数
void moveToThreadDemo::handleResult(int i)
{
	//接收woker传递过来的i,并进行显示
	QString qstr = QString("%1").arg(i);
	ui.lineEdit->setText(qstr);
}

2.效果

这里子线程将收到的数字1做了累加处理,并将结果传回主线程进行显示。
在这里插入图片描述

讨论

在实际操作的时候,发现moveToThread的方法确实有其独特的优势。但在实际应用场景也出现了一个问题,希望各位朋友一起来讨论讨论。
需求是多线程多串口的开发,由于串口数量不定,所以在线程建立,连接的过程中,后面的信号也会把前面的线程中槽函数触发。可以理解为在声明的时候,加了一个for循环,这里的循环次数就是串口的数量。
在这里插入图片描述
从运行结果来看,一共跑了6次任务。原因是第1个sendToWorker把第1个doWork(int)触发,由于是循环做的connect,第2个sendToWorker会把第1个和第2个doWork(int)触发,第3个sendToWorker会把第1个、第2个和第3个的doWork(int)触发,这就造成了系统资源的浪费。本意上是希望第i个sendToWorker只触发第i个doWork(int)。
在这里插入图片描述

目前的做法是在woker类里,给woker加入了私有变量ID,传递参数中也增加了ID,通过判断ID是否一致来决定是否执行。但是,不清楚是不是不是有更好的办法。解决方案如下:
Woker.h文件

#pragma once
#include "qobject.h"
class Woker :public QObject
{
	Q_OBJECT
public:
	Woker(int ID);
	~Woker();

private:
	int _wokerID;//加入私有ID
public slots:
    void doWork(int i);
signals:
	void SendToMain(int i);
};

Woker.cpp文件

#include "Woker.h"
#include <QDebug>
#include <QThread>

Woker::Woker( int ID)
{
	_wokerID = ID;
}

Woker::~Woker()
{
}

void Woker::doWork(int i)
{
    //增加ID判断
	if (i!=_wokerID)
	{
		qDebug() << "ThreadId" << QThread::currentThreadId() << "return";
		return;
	}
	qDebug() << "currentThreadId is" << QThread::currentThreadId();
	//Todo此处重写功能,此处用了+1功能代替
	i++;
	//发送信号
	emit SendToMain(i);
	qDebug() << "currentThreadId " << QThread::currentThreadId() << "run to end";
}

在这里插入图片描述
从结果可以看出,实际只有跑了3次任务,不相干的触发信号被return,从而解决多次触发,多次执行的问题。这个方法在使用的时候也要注意,任务复位后需要disconnect。不然重复执行动作,会出现多个同ID的woker,我这里用串口号(COM3/COM4/COM5)作为woker的ID。实际场景就是串口初始化,进行通信,关闭串口需要disconnect,不然再次使用该串口,进行初始化的时候,当前的连接的信号会触发前几次初始化连接的槽函数,此时ID是相同的。所以在串口关闭的时候,需要disconnect当前的连接。
在这里插入图片描述

工程源码

  1. RunDemo下载

参考资料

  1. QThread使用——关于run和movetoThread的区别
  2. Qt线程—QThread的使用–run和movetoThread的用法
  3. Qt线程实现分析-moveToThread vs 继承
  4. Qt多线程中的moveToThread()的简单用法

猜你喜欢

转载自blog.csdn.net/YRG1009/article/details/108546119
今日推荐