VS+QT多线程实现——run和moveToThread
写在前头:最近在学习多线程,以小白的视角写一些学习心得。也欢迎各位朋友勘误补充。
开发环境:VS2013+QT5
实现方法及特性
- run ——继承QThread的run函数,通过重写run()方法,实现任务功能。
- 使用run方便理解,简单的任务流程可以封装在run里面。
- run 是线程的入口,run的开始和结束意味着线程的开始和结束。
- 多线程访问变量或处理事务要考虑加锁。(目前还未涉及加锁,不展开讨论)
- 线程start()起来,理论上run函数会从头执行到结束。所以如果run中的任务是多步骤,中途需要数据反馈或ui变化,做异常处理相对麻烦。
- QThread只有run函数是在新线程里的,其他所有函数都在QThread生成的线程里!!!
- moveToThread——用moveToThread将继承于QObject的类转移到Thread里。
- 通过信号与槽连接,几乎不用考虑多线程的存在,也不用考虑使用QMutex来进行同步。
- 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当前的连接。
工程源码
参考资料
- QThread使用——关于run和movetoThread的区别
- Qt线程—QThread的使用–run和movetoThread的用法
- Qt线程实现分析-moveToThread vs 继承
- Qt多线程中的moveToThread()的简单用法
本文地址:https://blog.csdn.net/YRG1009/article/details/108546119