系列文章目录
Qt开发笔记(一)Qt的基础知识及环境编译(泰山派)
Qt学习笔记(二)Qt 信号与槽
Qt学习笔记(三)网络编程
Qt学习笔记(四)多线程
文章目录
- 系列文章目录
- 前言
- 一、QThead
- 1.1 QThead的引入
- 1.2 相关API
- 1.3 QThread的工作流程
- 二、继承QThread的代码实现
前言
在Qt中,多线程的处理一般是通过QThread类来实现。QThread代表一个在应用程序中可以独立控制的线程,也可以和进程中的其他线程共享数据。之前我们在Linux应用篇中提到过多线程的概念,使用它的好处是可以同时操作好几个目标,而不是因为上一个目标未结束使得需要的操作陷入阻塞状态。
一、QThead
1.1 QThead的引入
QThread 提供了线程启动、停止以及与其他对象通信的能力,我们可以利用主线程用于处理 GUI 操作,而长时间的耗时任务(如文件 I/O、网络请求、大数据处理等)可以放到其他线程中去执行,从而避免界面卡顿现象。QThread 是 Qt 提供的一个线程管理类,封装了原生的线程接口,使得线程的创建、启动、终止和通信更加直观和方便。
QThread 线程类是实现多线程的核心类。Qt有两种多线程的方法,其中一种是继承QThread的run()函数,另外一种是把一个继承于QObject的类转移到一个Thread里。两种方法区别不大,用起来都比较方便,但继承QObject的方法更加灵活,笔者这里偏向前者。
继承QThread
继承QThread是创建线程的一个普通方法。其中创建的线程只有run()方法在线程里的。其他类内定义的方法都在主线程内。
通过上面的图我们可以看到,主线程内有很多方法在主线程内,但是子线程,只有 run()方法是在子线程里的。run()方法是继承于QThread类的方法,用户需要重写这个方法,一般是把耗时的操作写在这个run()方法里面。
继承QObject的线程
与上一种方法不同,我们先写一个类继承 QObject,通过QObject::moveToThread()方法将它移到一个QThread线程里执行。那么可以通过主线程发送信号去调用QThread线程的方法如上图的fun4(),fun5()等等。这些方法都是在QThread线程里执行的。
1.2 相关API
函数名 | 描述 |
---|---|
run() | 线程入口函数 |
start() | 通过调用run()函数开始执行线程,操作系统根据优先级参数调度线程 |
currentThread() | 返回一个指向 管理当前执行线程 的QThread的指针 |
isRunning() | 若线程正在运行返回true,否则返回false |
sleep()/msleep()/usleep() | 实现线程休眠,单位为秒/毫秒/微秒 |
wait() | 阻塞线程 |
quit() | 请求线程退出事件循环,常用于安全关闭线程。 |
finished() | 当线程结束时会发出该信号,可以通过该信号来实现线程的清理工作 |
注:使用wait()函数后,线程会阻塞至直到满足以下任何一个条件:
- 与此QThread对象关联的线程已经完成执行(即当它从run()返回时),若线程已经完成,这个函数将返回true;若线程尚未启动,也返回true;
- 已经过了几毫秒,若时间是ULONG_MAX(默认值),那么等待永远也不会超时(线程必须从run()返回),若等待超时,此函数返回false与POSIX pthread_join() 函数类似terminate()| 终止线程的执行。线程可以立即终止,也可以不立即终止,取决于操作系统的调度策略。在terminate()之后使用QThread::wait()来确保终止
1.3 QThread的工作流程
就像前面提到的,笔者偏向继承QThread的写法,这里以这种为主。QThread 的核心流程是创建一个线程对象,将任务移动到该线程,然后启动线程以执行任务。QThread 内部维护了一个事件循环,确保线程可以响应事件和信号槽的触发。线程启动时自动调用 run() 方法,线程结束时会发送 finished 信号。
- 定义一个类继承 QThread 并重写 run() 函数
- 线程处理函数里面写入需要执行的复杂数据处理,需要注意以下几点:
- 确定 run() 函数需要执行的具体数据处理任务,例如文件读取、数据分析、图像处理、网络请求等。
- 处理流程设计:在 run() 中合理设计任务处理流程,比如是否需要循环、数据的预处理和后处理、错误处理等。
- 线程间通信:在任务处理过程中,可能需要将进度、结果或错误状态传递回主线程。可以使用信号槽机制实现这些交互。
- 资源释放:确保在任务完成时清理分配的资源,以防止内存泄漏或资源占用。 - 使用对象调用 start() 函数来启动线程
- 定义一个信号通知主线程执行完成
- 线程关闭与资源清理
二、继承QThread的代码实现
代码部分也是利用前段时间写的毕设拯救计划(二)基于QT的智能家居(Onenet云)中的代码演示,它主要是在 Qt 中使用 QThread 可以让 DHT11 的数据读取在后台线程中执行,从而避免阻塞主界面线程。如果大家不会dht11的话可以看一下笔者之前写的驱动Linux驱动开发笔记(五) 基于设备树与GPIO子系统(含单总线)的操作实验。
首先,新建一个类 DHT11ReaderThread,继承 QThread 并实现数据读取逻辑。
// dht11readerthread.h
#ifndef DHT11READERTHREAD_H
#define DHT11READERTHREAD_H
#include <QThread>
#include <QString>
class DHT11ReaderThread : public QThread
{
Q_OBJECT
public:
DHT11ReaderThread(QObject *parent = nullptr);
~DHT11ReaderThread();
protected:
// QThread 的主执行函数,前面提到了我们采用的方式主要是在run函数上
void run() override;
signals:
// 用于发送新数据的信号
void newData(QString temperature, QString humidity);
private:
// 用于控制线程的运行状态
bool keepRunning;
};
#endif // DHT11READERTHREAD_H
在 dht11readerthread.cpp的run函数中实现 DHT11 的数据读取逻辑,并在读取到数据后通过信号发送给主线程。
// dht11readerthread.cpp
#include "dht11readerthread.h"
#include "dht11.h"
#include <QDebug>
DHT11ReaderThread::DHT11ReaderThread(QObject *parent)
: QThread(parent), keepRunning(true)
{
dht11_init(); // 初始化 DHT11
}
DHT11ReaderThread::~DHT11ReaderThread()
{
keepRunning = false;
dht11_close(); // 关闭 DHT11
}
void DHT11ReaderThread::run()
{
while (keepRunning) {
char temperature;
char humidity;
// 读取 DHT11 数据
if (dht11_read(&humidity, &temperature) == 0) {
// 将读取到的数据格式化为字符串
QString tempStr = QString("%1°C").arg((int)temperature);
QString humiStr = QString("%1%").arg((int)humidity);
// 发出信号,传递新数据
emit newData(tempStr, humiStr);
} else {
qDebug() << "Failed to read data from DHT11.";
}
// 设置线程休眠 5 秒,控制读取频率
msleep(5000);
}
}
在 MainWindow 的头文件中声明 updateDisplay 槽函数,用于接收来自线程的温湿度数据并更新 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 slots:
void updateDisplay(const QString &temperature, const QString &humidity); // 更新显示的槽函数
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
在 MainWindow 中创建 DHT11ReaderThread 的实例,并连接信号以更新 QTextBrowser。
// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "dht11readerthread.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 创建 DHT11 读取线程实例
DHT11ReaderThread *readerThread = new DHT11ReaderThread(this);
// 连接线程的信号到更新 QTextBrowser 的槽
connect(readerThread, &DHT11ReaderThread::newData, this, &MainWindow::updateDisplay);
// 启动线程
readerThread->start();
}
MainWindow::~MainWindow()
{
delete ui;
}
// 更新 QTextBrowser 显示的槽函数
void MainWindow::updateDisplay(const QString &temperature, const QString &humidity)
{
QString displayText = QString("Temperature: %1\nHumidity: %2").arg(temperature).arg(humidity);
ui->textBrowser->setText(displayText);
}
注:这里强调一下,不要在main.cpp和mainwindow.cpp中重复实例化对象。
免责声明:本文参考了网上公开的部分资料,仅供学习参考使用,若有侵权或勘误请联系笔者