Qt多线程编程—使用Qt自身的线程类
使用Qt自身的线程类进行多线程编程,有两种方法。
**方法1概述:**首先用一个类继承QThread
类,然后重新改写QThread
类的虚函数run()
。只要实例化该类,然后调用函数start()
,就可以开启新的多线程(run()
会被自动调用),所以线程的具体实现过程放在run()
里面。
**方法二概述:**先用一个类继承QObject
类,然后在该类中添加一个成员函数working()
(名字无所谓)用来处理具体子线程的工作,在主线程调用QObjec
t类提供的moveToThread()
方法。
方法1具体步骤
1:需要创建一个线程类的子类,让其继承QT中的线程类 QThread,比如:
class MyThread:public QThread
{
......
}
2:重写父类的 run() 方法,在该函数内写子线程要处理的具体操作
class MyThread:public QThread
{
......
protected:
void run()
{
........
}
}
3:在主线程中创建子线程对象实例,关键字new
MyThread * subThread = new MyThread;
4:启动子线程, 调用 start() 方法
subThread->start();
方法1实操
这里以一个计数器的小案例为例,创建一个对话框项目,实现主线程和子线程之间的通信,启动线程计数时,右下加的计数器显示开始读秒,点击重置计数器按钮时,读秒数清零后继续计数,点击停止线程按钮时,计数停止。
(1)新建一个MyThread
类,继承QThread
类
(2)在MyThread.cpp
中重写MyThread
类的虚函数run()
,这就是子线程实现的具体功能函数
(3)对ui界面的四个按钮设置对应的槽函数on_pushButton_clicked() - on_pushButton_clicked3()
,在需要用到多线程的地方实例化MyThread
,当点击按钮“启动线程计数”(按钮1)时,调用MyThread::start()
函数,会开启对应的线程,自动运行重写的run()
函数。
//dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include "MyThread.h"
QT_BEGIN_NAMESPACE
namespace Ui {
class Dialog;
}
QT_END_NAMESPACE
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = nullptr);
~Dialog();
private slots:
void on_pushButton_clicked();
void on_pushButton_2_clicked();
void on_pushButton_3_clicked();
void DisplayMsg(int num);
private:
Ui::Dialog *ui;
MyThread *t;
signals:
void ResetSignal();
};
#endif // DIALOG_H
//dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"
#include <MyThread.h>
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::Dialog)
{
ui->setupUi(this);
t = new MyThread();//实例化
connect(t, SIGNAL(TestSignal(int)), this, SLOT(DisplayMsg(int)));
connect(this, SIGNAL(ResetSignal()), t, SLOT(ResetSlot()));
}
Dialog::~Dialog()
{
delete ui;
}
void Dialog::on_pushButton_clicked() // 线程开始按钮
{
t->start();
}
void Dialog::on_pushButton_2_clicked() //重置计数按钮
{
emit ResetSignal();
}
void Dialog::on_pushButton_3_clicked() //停止按钮
{
t->terminate();
}
void Dialog::DisplayMsg(int num)
{
ui->label->setText(QString::number(num));
}
在这个案例中,通过改变number
的值后,返回到主线程中再在ui界面上进行显示,为什么不直接在子线程中进行对ui的操作呢?这是因为Qt中的子线程无权操作程序中的窗口类型对象, 这是不允许的,所以都要通过主线程来操作。
方法2具体步骤
1:创建一个新的类,让这个类从QObject派生
class MyWork:public QObject
{
.......
}
2:在这个类中添加一个公共的成员函数,函数体就是我们要子线程中执行的具体操作
class MyWork:public QObject
{
public:
.......
// 函数名自己指定, 叫什么都可以, 参数可以根据实际需求添加
void working();
}
3:在主线程中创建一个QThread对象, 这就是子线程的对象
QThread* sub = new QThread;
4:在主线程中创建工作的类对象**(千万不要指定给创建的对象指定父对象)**
MyWork* work = new MyWork(this); // error
MyWork* work = new MyWork; // ok
5:将MyWork对象移动到创建的子线程对象中, 需要调用QObject类提供的moveToThread()方法
// void QObject::moveToThread(QThread *targetThread);
// 如果给work指定了父对象, 这个函数调用就失败了
// 提示: QObject::moveToThread: Cannot move objects with a parent
work->moveToThread(sub); // 移动到子线程中工作
方法2实操
同样也以一个简单的计数器来说明,点击开始,数值显示区域开始计数。
(1)在MyThread.h
中新建一个MyThread
类,继承QObject
类,这一点就与第一种方法不同了,继承的类不同。这个类中添加一个公共的成员函数working()
,函数体就是我们要子线程中执行的具体操作,在MyThread.cpp
中实现具体代码。
(2)在主线程中创建一个QThread对象sub, 这就是子线程的对象,再创建一个工作类对象work,将工作类对象移动到创建的子线程当中去work->moveToThread(sub);
随后调用start()
启动函数。此外,在Ui界面中设置按钮pushButton
和标签label
,运行项目就可以得到一个在子线程中运行的计数器。
//mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
#include "MyThread.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//创建线程类对象
QThread* sub = new QThread(this);
//创建工作类对象 注意不要指定父对象
MyThread* work = new MyThread;
//将工作类对象移动到创建的子线程当中去
work->moveToThread(sub);
// 启动线程
sub->start();
//点击开始按钮,开始工作
connect(ui->pushButton,&QPushButton::clicked,work,&MyThread::working);
//显示数据
connect(work,&MyThread::curNumber,this,[=](int num)
{
ui->label->setNum(num);
});
//释放资源
connect(this,&MainWindow::destroyed,this,[=](){
sub->quit();
sub->wait();
sub->deleteLater(); //等价于 delete sub;
work->deleteLater();
});
}
MainWindow::~MainWindow()
{
delete ui;
}
//mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
注意:
程序结束后对资源的释放时很关键的一步,不然程序容易崩溃,本案例中添加了如下代码,用以释放创建的线程类对象sub和工作类对象work,当窗口关闭的时候执行。
两种方法对比
相比而言,第一种方式在实现上是比较简单的,但是也有弊端,假设要在一个子线程中处理多个任务,所有的处理逻辑都需要写到run()函数中,这样该函数中的处理逻辑就会变得非常混乱,不太容易维护。第二种方法实现就比较灵活,对于多个任务(比如显示多个界面)各分配一个线程去执行。这样就避免了自定义好多个类继承自QThread类,从而可以避免冗余。所以当处理的任务多时,优先考虑第二种方法,当任务量比较少时,可以采用第一种方法。
注:
本博客参考了一个叫大丙的博主的教程大丙课堂-QT线程篇,他有好多优质的文章,大家可以学习参考。