阅读导航
引言
从文件操作的深度探索转向Qt多线程编程的广阔领域,我们即将踏上一场新的旅程。多线程技术将助力我们的应用程序并行处理任务,提升性能与响应速度。在Qt中,多线程编程不仅安全高效,还借助其独特的信号与槽机制实现了线程间的优雅通信。
一、Qt多线程概述
在Qt框架中,多线程的处理主要通过QThread
类来实现。QThread
代表了在应用程序中可以独立运行和控制的线程,这些线程能够与其他线程(包括主线程)共享应用程序的数据,但各自执行其独立的控制流。通过QThread
对象,开发者可以管理程序中的一个或多个线程,每个QThread
对象都负责在其自己的线程中执行代码。这种方式允许开发者编写更加复杂、响应更快的应用程序,因为耗时操作(如网络请求、大量数据处理等)可以在单独的线程中执行,而不会阻塞主线程(通常是GUI线程),从而保持用户界面的流畅性。
二、QThread常用 API
以下是您提供的Qt中QThread
类相关方法和功能的表格形式输出:
API接口 | 描述 |
---|---|
run() |
线程的入口函数。开发者需要重写此函数来定义线程执行的任务。 |
start() |
通过调用run() 函数开始执行线程。操作系统将根据优先级参数调度线程。如果线程已经在运行,则此方法不执行任何操作。 |
currentThread() |
返回一个指向管理当前执行线程的QThread 的指针。 |
isRunning() |
如果线程正在运行则返回true ;否则返回false 。 |
sleep() / msleep() / usleep() |
使线程休眠,单位分别为秒、毫秒、微秒。这些函数允许线程暂停执行指定的时间。 |
wait() |
阻塞调用它的线程,直到与此QThread 对象关联的线程完成执行(即从run() 返回),或者等待时间已过(如果指定了等待时间)。如果线程已完成或尚未启动,则返回true ;如果等待超时,则返回false 。 |
terminate() |
尝试立即终止线程的执行。但请注意,由于操作系统的调度策略,线程可能不会立即终止。在调用terminate() 后,应使用QThread::wait() 来确保线程已真正停止。然而,通常不推荐使用terminate() ,因为它可能会导致资源泄露或其他不可预知的行为。 |
finished() |
当线程结束时会发出此信号。可以通过连接此信号来执行清理工作或其他必要的操作。 |
三、使用线程
1. 一般步骤
定义子类继承自
QThread
:首先,你需要创建一个自定义的类,该类继承自QThread
。这个自定义类将代表一个新的线程,用于执行与主线程(通常是GUI线程)不同的任务。重写
run()
方法:在自定义的QThread
子类中,你需要重写run()
方法。这个run()
方法将成为新线程的入口点,即新线程开始执行时将调用的方法。在run()
方法内部,你可以编写需要在新线程中执行的复杂数据处理或其他任务。启动线程:要启动线程,你不能直接调用自定义类中的
run()
方法,因为这样做并不会创建新的线程来执行run()
方法,而是会在当前线程(可能是主线程)中同步执行它。相反,你应该创建自定义QThread
子类的一个实例,并通过调用这个实例的start()
方法来启动新线程。start()
方法内部会由Qt框架负责创建新的线程,并在该线程中调用run()
方法。线程间通信:当
run()
方法中的任务执行完毕后,你可能需要通知主线程或其他线程任务已完成。这通常通过Qt的信号与槽机制来实现。你可以在自定义的QThread
子类中定义一个或多个信号,当run()
方法执行结束或达到某个特定状态时,发射这些信号。然后,在主线程或其他线程中连接这些信号到相应的槽函数,以执行清理工作、更新UI或其他必要的操作。线程管理:最后,关于线程的关闭,
QThread
提供了quit()
和exit()
方法来优雅地请求线程退出。然而,更常见的做法是,让run()
方法执行完毕后自然返回,从而结束线程的执行。如果确实需要强制终止线程,应谨慎使用terminate()
方法,因为它可能会导致不可预知的行为,包括数据不一致或资源泄露。在大多数情况下,推荐使用更安全的线程退出机制,如条件变量、互斥锁和事件循环来控制线程的执行和终止。
2. LED倒计时实例
(1)基本需求
我们通过一个具体的倒计时示例来阐述线程的重要性。在这个场景中,LED控件被用来动态显示一个数值,它会以每秒钟减少1的速率进行更新。
要求:多线程可以在不阻塞主线程的情况下,独立地控制LED控件的数值更新。这样,即使LED控件的数值在不断变化,应用程序的其他部分(如用户界面)也能保持响应,不会出现卡顿或冻结现象。
🚨🚨注意:在Qt中直接在线程函数中操作UI界面是不被允许的。在Qt中处理这种情况,通常的做法是将数据处理和UI更新分开处理:正确使用信号与槽机制是实现线程间通信和UI更新的推荐方式。
(2)新建Qt项目,设计UI界面
(3)新建⼀个继承于QThread的类
(4)具体实现代码
⭕thread.h
#ifndef THREAD_H
#define THREAD_H
#include <QWidget>
#include <QThread>
class Thread : public QThread
{
Q_OBJECT
public:
Thread();
// 重要的目的是重写父类的 run 方法.
void run();
signals:
void notify();
};
#endif // THREAD_H
⭕thread.cpp
#include "thread.h"
Thread::Thread()
{ }
void Thread::run()
{
// 在这个 run 中, 能否直接去进行修改界面内容呢?不可以的!!!
// 虽然不可以修改界面, 但是可以针对时间来进行计时.
// 当每到了一秒钟的时候, 通过信号槽, 来通知主线程, 负责更新的界面内容.
for (int i = 0; i < 10; i++) {
// sleep 本身是 QThread 的成员函数, 就可以直接调用.
sleep(1);
// 发送一个信号, 通知主线程.
emit notify();
}
}
⭕widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "thread.h"
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
Thread thread;//创建一个线程实例
void handle();
};
#endif // WIDGET_H
⭕widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 连接信号槽, 通过槽函数更新界面.
connect(&thread, &Thread::notify, this, &Widget::handle);
// 要启动一下线程.
thread.start();
}
Widget::~Widget()
{
delete ui;
}
void Widget::handle()
{
// 此处修改界面内容.
int value = ui->lcdNumber->intValue();
value--;
ui->lcdNumber->display(value);
}