Qt (18)【Qt 多线程 QThread类】

发布于:2024-09-18 ⋅ 阅读:(109) ⋅ 点赞:(0)

引言

从文件操作的深度探索转向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. 一般步骤

  1. 定义子类继承自QThread:首先,你需要创建一个自定义的类,该类继承自QThread。这个自定义类将代表一个新的线程,用于执行与主线程(通常是GUI线程)不同的任务。

  2. 重写run()方法:在自定义的QThread子类中,你需要重写run()方法。这个run()方法将成为新线程的入口点,即新线程开始执行时将调用的方法。在run()方法内部,你可以编写需要在新线程中执行的复杂数据处理或其他任务。

  3. 启动线程:要启动线程,你不能直接调用自定义类中的run()方法,因为这样做并不会创建新的线程来执行run()方法,而是会在当前线程(可能是主线程)中同步执行它。相反,你应该创建自定义QThread子类的一个实例,并通过调用这个实例的start()方法来启动新线程。start()方法内部会由Qt框架负责创建新的线程,并在该线程中调用run()方法。

  4. 线程间通信:当run()方法中的任务执行完毕后,你可能需要通知主线程或其他线程任务已完成。这通常通过Qt的信号与槽机制来实现。你可以在自定义的QThread子类中定义一个或多个信号,当run()方法执行结束或达到某个特定状态时,发射这些信号。然后,在主线程或其他线程中连接这些信号到相应的槽函数,以执行清理工作、更新UI或其他必要的操作。

  5. 线程管理:最后,关于线程的关闭,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);
}

(5)执行效果

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到