1. Qt 多线程概述
-
Qt 默认只有一个主线程,该线程既要处理窗口移动,又要处理各种运算。
-
当要处理的运算比较复杂时,窗口就无法移动,所以在处理程序时在很多情况下都需要使用多线程来完成。
示例:移动窗口和复杂循环
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->startBtn, &QPushButton::clicked, [=](){
// 模拟复杂运算
for (int i = 1; i <= 10000000; i++)
{
ui->lcdNumber->display(QString::number(i));
}
});
}
2. QThread
QThread 类用来创建子线程
2.1 用法-1
方式: 单独定义线程类,线程类完成全部的业务逻辑,主线程只负责启动子线程和退出子线程
使用步骤:
-
创建一个类(MyThread),并继承 QThread
-
QThread 中有一个 protected 级别的 virtual void run(), 必须在该函数中实现线程业务逻辑
-
启动线程必须使用 start() 槽函数
-
在 MyThread 中可以定义线程执行完成信号(isDone), 在 run函数结束前发射isDone信号,来处理线程执行完毕的逻辑
示例:
1.创建一个类,并继承 QThread
class MyThread : public QThread
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = nullptr);
protected:
// 线程处理函数,不能直接调用,需要使用start来启动线程
void run();
signals:
// 自定义信号,当线程执行完毕时可以发射该信号,告诉系统线程已执行完毕
void isDone();
public slots:
};
2.在 run 函数中实现业务逻辑
void MyThread::run()
{
// 模拟复杂业务逻辑
for (int i = 0; i < 10000000; i++)
{
qDebug() << i;
}
// 线程结束时发送 isDone 信号
emit this->isDone();
}
3.在主窗口实例化 MyThread 类,并调用 start方法,来启动线程
4.链接myThread 对象 和 isFinished 信号
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
myThread = new MyThread(this);
connect(ui->startBtn, &QPushButton::clicked, [=](){
// 通过 start 来启动线程,不能直接调用 run
myThread->start();
});
// 当子线程结束时发送 isDone 信号,连接信号和槽 可以处理子线程结束之后的事情
connect(myThread, &MyThread::isDone, [=](){
qDebug() << "线程结束";
myThread->quit();
});
}
2.2 用法-2
方式:主窗口与线程类配合完成业务逻辑。
-
子类提供线程能力,
-
主窗口利用子类提供的线程能力进行业务处理。 同时还需要开启和关闭子线程
示例: 计时器(线程版)
实现思路: 子类每秒向主窗口发送一个信号,主窗口接收到信号后进行累加操作,并将累加结果显示到窗口中
// mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QObject>
class MyThread : public QObject
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = nullptr);
void sendMsg();
signals:
void mySignal();
public slots:
};
#endif // MYTHREAD_H
// mythread.c
#include "mythread.h"
#include <QThread>
//核心功能: 没个一段时间主动向主窗口发送一个信号
MyThread::MyThread(QObject *parent) : QObject(parent)
{
}
void MyThread::sendMsg()
{
while (true) {
QThread::sleep(1);
emit this->mySignal();
}
}
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "mythread.h"
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget();
signals:
void startSibThread();
private:
Ui::Widget *ui;
MyThread *myThread;
int num;
};
#endif // WIDGET_H
//widget.c
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QThread>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
this->myThread = new MyThread;
QThread *thread = new QThread(this);
this->myThread->moveToThread(thread);
connect(this->myThread,&MyThread::mySignal, [this](){
this->num++;
ui->lcdNumber->display(this->num);
});
connect(this, &Widget::startSibThread,this->myThread, &MyThread::sendMsg);
connect(ui->pushButton,&QPushButton::clicked,[=](){
thread->start();
emit this->startSibThread();
});
}
Widget::~Widget()
{
delete ui;
}
实现步骤:
1.创建子类(MyThread),继承于 QObject。
==在该类中设置一个能够每秒钟发送一次信号的函数, 功能就是提供线程能力==
2.在主窗口中引入MyThread,并将其对象放在线程(QTread)中进行调用 ( 注意:此步并没有启动线程)
3.主窗口中接收信号,在匹配的槽函数中进行业务处理
4.点击按钮时启动线程 ( 使用该方法启动线程时,必须使用信号和槽的方式 )
5.关闭定时器: ① 结束线程 ② 结束 while 循环
1.创建子类(MyThread),继承于 QObject (在该类中设置一个能够每秒钟发送一次信号的函数)
class MyThread : public QObject
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = nullptr);
// 线程处理函数
void dealThread();
signals:
// 提供给主窗口的信号
void mySignal();
public slots:
};
void MyThread::dealThread()
{
while (1)
{
QThread::sleep(1);
emit this->mySignal();
qDebug() << "子线程号:" << QThread::currentThread();
}
}
2.在主窗口中引入MyThread,并将其对象放在线程中进行调用
3.主窗口中接收信号,在匹配的槽函数中进行业务处理
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
// 实例化自定义的线程,但是不能指定父对象
myThread = new MyThread;
// 创建子线程
qThread = new QThread(this);
// 将自定义的线程加入到子线程中, 就是将自定义线程移动到系统提供的线程对象中
// 如果实例化 MyThread 时指定了父对象就不能移动了
myThread->moveToThread(qThread);
// 处理线程发射的信号
connect(myThread, &MyThread::mySignal, [=](){
static int i = 0;
i++;
ui->lcdNumber->display(QString::number(i));
});
}
4.点击按钮时启动程序
该方式启动线程时需要调用 start() 方法
同时发送自定义信号 startSubThread
void Widget::on_startBtn_clicked()
{
// 启动线程
qThread->start();
emit this->startSubThread();
}
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
......
myThread->moveToThread(qThread);
// 处理线程发射的信号
connect(myThread, &MyThread::mySignal, [=](){
static int i = 0;
i++;
ui->lcdNumber->display(QString::number(i));
});
qDebug() << "主线程号:" << QThread::currentThread();
// 在 startSubThread 信号中调用子类提供的信号
connect(this, &Widget::startSubThread, myThread, &MyThread::dealThread);
}
5.关闭定时器:① 结束线程 ② 结束 while 循环
在 MyThread 中设置 isStop 属性,用来控制线程和循环的开启或者关闭
class MyThread : public QObject
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = nullptr);
// 线程处理函数
void dealThread();
// 修改isStop状态
void setIsStop(bool flag);
signals:
// 提供给主窗口的信号
void mySignal();
public slots:
private:
// 是否关闭线程的标志 true 为关闭, false 为不关闭
bool isStop = false;
};
void MyThread::setIsStop(bool flag)
{
this->isStop = flag;
}
void Widget::on_stopBtn_clicked()
{
myThread->setIsStop(true);
qThread->quit();
qThread->wait();
}
用法小结:
-
创建 MyThread 类(继承QObject即可)
① 提供一个信号(void mySignal())
② 定义了一个方法(void dealThread())
核心功能:使用死循环,每隔一段时间之后就发射一个信号 (角色:监工)
-
在窗口类中(widget)
① 实例化 MyThread 得到 myThread对象
② 监听 mySignal 信号,一旦监听到信号之后就去实现业务的主体功能(i++,lcd->display(i))
③ 实例化 QThread 得到 qThread对象,再将 myThread转移到qThread中,这个时候就有了线程的功能
④ 在开始按钮上绑定信号和槽,在槽函数中调用 qThread->start() 来启动线程
⑤ 调用 MyThread::dealThread 方法,必须要在 Widget 类中使用自定义信号(startSubThread)的方式,并在其处理信号的槽函数位置调用 MyThread::dealThread 方法
2.3 轮播图
绘图基础回顾:
-
绘制图片要在 void paintEvent(QPaintEvent *event) 事件中
-
使用到的类 QPainter 、 QPixmap
-
窗口中的图片需要进行重绘时,需要使用 QWidget 的 update 方法
第一步: 通过点击信号切换图片
核心思路: 将图片资源地址保存在一个 QVector 中,通过索引号来访问; 切换到下一张图片就是 索引号自增1 ,再调用 update() 方法重绘图片
① 使用数组保存图片资源地址
② 使用索引来控制显示哪张图片
③ 在 paintEvent 事件中绘制图片
④ 点击下一张按钮时对索引号进行自增1,再重绘图片
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget();
// 绘图事件
void paintEvent(QPaintEvent *);
private:
Ui::Widget *ui;
// imgPaths 用来保存图片资源路径
QVector<QString> imgPaths;
// index 用来控制显示 imPaths 中的那张图片
int index = 0;
};
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
this->resize(600, 600);
// 将数组保存在一个 QVector 中
imgPaths = {
":/images/1.jpg", ":/images/2.jpg", ":/images/3.jpg", ":/images/4.jpg"
};
// 点击下一张按钮时对索引号自增1, 注意进行边界检测
connect(ui->nextBtn, &QPushButton::clicked, [=](){
this->index = ++this->index == 4 ? 0 : this->index;
// 重绘图片
this->update();
});
}
void Widget::paintEvent(QPaintEvent *)
{
// 根据索引号绘制图片
QPainter painter(this);
QPixmap pix;
pix.load(this->imgPaths[this->index]);
painter.drawPixmap(QRect(0, 0, 200, 200), pix);
}
第二步: 使用线程替换 next 按钮
核心思路:创建 MyThread 类,每三秒向主窗口发送一个信号,主窗口每次接收到该信号时自动对索引号自增1,接着在重绘图片
-
创建 MyThread 类,每3秒向窗口类
-
主窗口监听到MyThread发送的信号后,对索引号进行自增1,接着在重绘图片
-
程序启动时,开启子线程
1.创建MyThread 类,每3秒向窗口类
class MyThread : public QObject
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = nullptr);
// 定义线程函数
void dealThread();
signals:
// 自定义信号,每三秒向主窗口发送一次信号
void mySignal();
};
void MyThread::dealThread()
{
// 每 3 秒向窗口发送一次信号
while (1)
{
QThread::sleep(3);
emit this->mySignal();
}
}
2.主窗口监听到MyThread发送的信号后,对索引号进行自增1,接着在重绘图片
class Widget : public QWidget
{
Q_OBJECT
public:
...
signals:
// 启动子线程的信号
void startSubThread();
private:
...
MyThread *myThread;
QThread *qThread;
};
// 实例化自定义类 和 线程类,并将自定义类添加到线程中
myThread = new MyThread;
qThread = new QThread(this);
myThread->moveToThread(qThread);
// 通过线程调整索引号
connect(myThread, &MyThread::mySignal, [=](){
this->index = ++this->index == 4 ? 0 : this->index;
this->update();
});
3.程序启动时,开启子线程
// 启动子线程就直接调用 dealThread 槽函数
connect(this, &Widget::startSubThread, myThread, &MyThread::dealThread);
// 启动子线程
qThread->start();
emit this->startSubThread();
2.4 轮播图实现代码(全)
//mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QObject>
class MyThread : public QObject
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = nullptr);
void sendMsg();
void setIsRun(bool flag);
signals:
void mySignal();
public slots:
private:
//true 循环发射信号,false 停止循环发送信号
bool isRun = true;
};
#endif // MYTHREAD_H
//mythread.c
#include "mythread.h"
#include <QThread>
MyThread::MyThread(QObject *parent) : QObject(parent)
{
}
void MyThread:: sendMsg()
{
while(this->isRun)
{
QThread::sleep(3);
emit this->mySignal();
}
}
void MyThread::setIsRun(bool flag)
{
this->isRun = flag;
}
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QVector>
#include "mythread.h"
#include <QThread>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget();
void enterEvent(QEvent *);
void leaveEvent(QEvent *);
private slots:
void on_pushButton_2_clicked();
void on_pushButton_clicked();
signals:
void startSubThread();
private:
Ui::Widget *ui;
MyThread *myThread;
QThread *qThread;
QVector<QString>imgList;
int index = 0;
};
#endif // WIDGET_H
//widget.c
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
this->imgList = {":/images/ashe.png",":/images/jax.png",":/images/vn.png"};
ui->label->setPixmap(QPixmap(this->imgList[this->index]));
ui->label->setScaledContents(true);
//1.准备线程
this->myThread = new MyThread;
this->qThread = new QThread;
this->myThread->moveToThread(this->qThread);
connect(this,&Widget::startSubThread,this->myThread,&MyThread::sendMsg);
//3.处理监工提供信号
connect(this->myThread,&MyThread::mySignal,[this](){
this->index++;
if (this->index == this->imgList.size())
{
this->index = 0;
}
ui->label->setPixmap(QPixmap(this->imgList[this->index]));
});
//2. 启动子线程
this->qThread->start();
emit this->startSubThread();
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_2_clicked()
{
this->index++;
if (this->index == this->imgList.size())
{
this->index = 0;
}
ui->label->setPixmap(QPixmap(this->imgList[this->index]));
}
void Widget::on_pushButton_clicked()
{
this->index--;
if (this->index == -1)
{
this->index = this->imgList.size() - 1;
}
ui->label->setPixmap(QPixmap(this->imgList[this->index]));
}
//鼠标进入停止线程
void Widget::enterEvent(QEvent *)
{
this->qThread->quit();
this->myThread->setIsRun(false);
}
//鼠标离开启动线程
void Widget::leaveEvent(QEvent *)
{
this->myThread->setIsRun(true);
this->qThread->start();
emit this->startSubThread();
}