QT6之多线程——子类化QObject和子类化QThread

2024-05-01 21:36

本文主要是介绍QT6之多线程——子类化QObject和子类化QThread,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

备注:本文重点不是教怎么写多线程,这个官方示例依和网上示例一大把。 

众所周知QT多线程有两种方法,一个是子类化QThread,另一个是子类化QObject。

Qt官方实际上是推荐后者,但实际运用中两者各有优劣和场景,并没有绝对的替代彼此

多线程的必要性不言而喻,Qt默认的线程在Qt中称之为窗口线程,也叫主线程,负责窗口事件处理(鼠标事件、键盘事件等等)或者窗口控件数据的更新子线程负责后台的业务逻辑处理,子线程中不能对窗口对象做任何操作,这些事情需要交给窗口线程处理.

最常见的现象就是,当你新建项目后给QMainWindow或者QWidget对象的构造函数引入复杂耗时的逻辑时,然后再试着拖动窗口顶部结果卡死了。因为事件循环被阻塞了拖动窗口的事件执行不到了,然后就卡死了……

1、优劣对比

类别优势劣势
子类化QThread

1、简单直观

2、控制权高

3、线程独立性

1、继承限制

2、信号槽连接不安全

子类化QObject

1、灵活多样性

2、信号槽机制

默认支持事件循环

3、多继承支持

1、线程控制复杂

2、线程间通信需要额外处理

重点区别:继承QObject的多线程整个类都是在新的线程中,而继承QThread的多线程仅run函数内部在新的线程中

 2、子类化QThread

QT虽然推崇后者但子类化QThread的方法截至最新版的Qt6仍然官方支持,在文档中也明确标注了两种实现方式,QThread仍然有很多人使用,我认为最重要的原因就在于它实现简单易于理解。

优: 

  1. 简单直观: QThread是Qt提供的线程类,子类化QThread可以直接获得一个完整的线程对象,方便使用。
  2. 控制权高: 子类化QThread可以方便地控制线程的启动、停止、暂停、恢复等操作,封装了线程管理的相关方法。
  3. 线程独立性: 每个子类化QThread的实例都拥有自己独立的线程执行环境,不会受到其他线程的影响。

 劣:

  1. 继承限制: 由于C++的单继承限制,当子类化QThread时,就不能再继承其他类了,这可能会限制代码的灵活性和复用性。
  2. 信号槽连接不安全: 在子类化QThread中使用信号槽连接时,需要注意线程安全的问题,因为信号槽连接是跨线程的,可能会引发竞态条件等问题。

 子类化QThread实现一个线程如下就这么简单,退出线程时建议用注释了的标志位+锁的方式退出线程;

#ifndef MYTHREAD_H
#define MYTHREAD_H#include <QThread>
#include <QDebug>class MyThread : public QThread
{Q_OBJECT
public:explicit MyThread(QObject *parent = nullptr) : QThread(parent) {}// bool m_isCanRun;
protected:void run() override {qDebug() << "Thread started.";for (int i = 0; i < 100; ++i) {//m_isCanRun=false;//QMutexLocker locker(&m_lock);//if(!m_isCanRun)//在每次循环判断是否可以运行,如果不行就退出循环//{//    return;// }QThread::sleep(1); // 模拟耗时操作qDebug() << "In thread:" << QThread::currentThreadId() << " - Count:" << i;}qDebug() << "Thread finished.";}
};#endif // MYTHREAD_H-----------------------------------------------------------------------------------------#include <QCoreApplication>
#include "MyThread.h"int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);MyThread thread;thread.start();
//stop时m_isCanRun置为truereturn a.exec();
}

 QThread只有run函数是在新线程里的,其他所有函数都不在新线程里,看似简单但是也有弊端,假设要在一个子线程中处理多个任务,所有的处理逻辑都需要写到run()函数中,这样该函数中的处理逻辑就会变得非常混乱,不太容易维护 。

3、子类化QObject

优:

  1. 灵活多样: QObject是Qt中的基础类,它提供了丰富的功能和特性,可以用于创建各种不同类型的对象,包括线程对象。
  2. 信号槽机制: QObject提供了强大的信号槽机制,方便对象之间的通信,可以轻松实现跨线程的通信。
  3. 多继承支持: 由于QObject不受C++的单继承限制,因此可以在子类化QObject时继承其他类,提高了代码的灵活性和复用性。

劣:

  1. 线程控制复杂: 子类化QObject创建的对象并不直接拥有线程执行环境,因此在创建自定义线程时,需要额外管理线程的启动、停止等操作,相对较为复杂。
  2. 线程间通信需要额外处理: 子类化QObject创建的对象并不自带线程执行环境,因此在实现多线程时,需要额外处理线程的管理和通信,可能会增加一些复杂性。

4、选择建议

原则是“哪个熟练用哪个”,最合理的情况是这样:

  1. 如果只是创建一个简单的线程对象,执行一些任务,并且不需要与其他类进行继承关系,那么子类化QThread可能更为合适。
  2. 如果需要创建的对象既要拥有线程执行环境,又要拥有QObject的功能和特性,并且可能需要与其他类进行继承关系,那么子类化QObject可能更为适合。

5、示例

5.1 信号和槽机制通信无需互斥锁或信号量等同步机制

当需要与主线程进行通信时,可以通过信号和槽机制实现,而无需显式地使用互斥锁或信号量等同步机制,使得线程间通信更加简洁和安全。

假设在Worker对象的工作过程中,需要定期向主线程发送一些信息以更新界面。Worker对象可以定义一个信号,在需要发送信息时发射这个信号,而主线程可以连接这个信号到一个槽函数,以便在主线程中更新UI。 

// Worker.h
#ifndef WORKER_H
#define WORKER_H#include <QObject>
#include <QThread>
#include <QDebug>class Worker : public QObject
{Q_OBJECTpublic:Worker() {}public slots:void doWork() {for (int i = 0; i < 5; i++) {qDebug() << "Working in thread" << QThread::currentThreadId();emit progressChanged(i); // 发射信号通知主线程工作进度QThread::sleep(1);  // 模拟耗时任务}emit finished();}signals:void progressChanged(int value); // 声明一个信号,用于通知主线程工作进度void finished();
};#endif // WORKER_H

在主线程中,可以将这个信号连接到一个槽函数,以便在接收到信号时更新UI 

// MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QPushButton>
#include <QThread>
#include "Worker.h"class MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr) : QMainWindow(parent) {QPushButton *button = new QPushButton("Start Thread", this);button->setGeometry(100, 100, 100, 30);Worker *worker = new Worker();QThread *thread = new QThread();worker->moveToThread(thread);connect(thread, &QThread::started, worker, &Worker::doWork);connect(worker, &Worker::finished, thread, &QThread::quit);connect(worker, &Worker::finished, worker, &QObject::deleteLater);connect(thread, &QThread::finished, thread, &QObject::deleteLater);// 将Worker对象的progressChanged信号连接到主线程的槽函数connect(worker, &Worker::progressChanged, this, &MainWindow::updateProgress);connect(button, &QPushButton::clicked, thread, &QThread::start);}public slots:void updateProgress(int value) {// 在这里更新主界面上的进度条或其他UI组件qDebug() << "Progress changed:" << value;}~MainWindow() {// 在主窗口销毁时,停止线程并等待线程结束thread->quit();thread->wait();}
};#endif // MAINWINDOW_H

 5.2 线程默认自启动和销毁

1、启动:

在子类化QObject的线程中启动线程不是非得用信号和槽,默认start也可以启动。

2、重点是销毁:

在线程创建之后,这个的销毁是在主线程里进行,而是通过槽deleteLater进行安全的销毁。

而子类化QObject的线程中引入了两个对象,一个是QThread的对象(如下图thread),另一个是QObject的对象(如下图worker),它们的销毁有两种:

2.1 若QThread的对象在堆里即用指针或者说new实例化

QThread的对象必须通过槽函数deleteLater 安全销毁,并在析构函数中调用quit和wait;

2.2 若QThread的对象的存放在栈里

QThread的对象没必要调用槽函数deleteLater 安全销毁,但需在析构函数中调用quit和wait;

两种情况共性:QObject的对象一般都在堆里,直接调用槽函数deleteLater 安全销毁即可。

// MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QThread>
#include "Worker.h"class MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr) : QMainWindow(parent) {// 创建 Worker 对象和 QThread 对象worker = new Worker();thread = new QThread();// 将 Worker 对象移动到新的线程中worker->moveToThread(thread);// 连接信号和槽connect(thread, &QThread::started, worker, &Worker::doWork);connect(worker, &Worker::finished, thread, &QThread::quit);connect(worker, &Worker::finished, worker, &QObject::deleteLater);connect(thread, &QThread::finished, thread, &QObject::deleteLater);// 启动线程thread->start();}~MainWindow() {// 在主窗口销毁时,停止线程并等待线程结束thread->quit();thread->wait();}private:Worker *worker;QThread *thread;
};#endif // MAINWINDOW_H

 5.3 多个Worker对象—多个线程管理

创建多个Worker对象并将它们移动到不同的线程中执行,建议使用QList指针管理。

注意对应的调用槽函数deleteLater 安全销毁,以及quit和wait。

// MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QThread>
#include "Worker.h"class MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr) : QMainWindow(parent) {// 创建三个 Worker 对象和对应的 QThread 对象for (int i = 0; i < 3; ++i) {Worker *worker = new Worker();QThread *thread = new QThread();// 将 Worker 对象移动到对应的线程中worker->moveToThread(thread);// 连接信号和槽connect(thread, &QThread::started, worker, &Worker::doWork);connect(worker, &Worker::finished, thread, &QThread::quit);connect(worker, &Worker::finished, worker, &QObject::deleteLater);connect(thread, &QThread::finished, thread, &QObject::deleteLater);// 启动线程thread->start();// 保存 Worker 对象和 QThread 对象workers.append(worker);threads.append(thread);}}~MainWindow() {// 在主窗口销毁时,停止所有线程并等待线程结束for (int i = 0; i < workers.size(); ++i) {threads[i]->quit();threads[i]->wait();}}private:QList<Worker*> workers;QList<QThread*> threads;
};#endif // MAINWINDOW_H

5.4 多个Worker对象—一个线程管理

不是每个Worker对象都必须对应一个独立的QThread对象。通常情况下,也可以将多个Worker对象移动到同一个QThread对象中执行,这样可以更好地利用系统资源,减少线程创建和销毁的开销。

请注意以下示例中调用thread的槽函数deleteLater安全销毁的位置,在for循环外部。

在Worker对象的finished()信号发出后,连接到QObject::deleteLater槽的代码应该在连接到thread对象的finished()信号之前,因为Worker对象在QThread结束时会被删除,如果先断开Worker对象的信号槽连接,再删除thread对象,会导致Worker对象的deleteLater操作无法执行,从而可能造成内存泄漏。

// MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QThread>
#include "Worker.h"class MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr) : QMainWindow(parent) {// 创建一个 QThread 对象thread = new QThread();// 创建三个 Worker 对象for (int i = 0; i < 3; ++i) {Worker *worker = new Worker();// 将 Worker 对象移动到 QThread 对象中worker->moveToThread(thread);// 连接信号和槽connect(thread, &QThread::started, worker, &Worker::doWork);connect(worker, &Worker::finished, thread, &QThread::quit);connect(worker, &Worker::finished, worker, &QObject::deleteLater);// 保存 Worker 对象workers.append(worker);}// 连接 thread 对象的 finished() 信号// 在线程结束后,删除 thread 对象connect(thread, &QThread::finished, thread, &QObject::deleteLater);// 启动线程thread->start();}~MainWindow() {// 在主窗口销毁时,停止线程并等待线程结束thread->quit();thread->wait();}private:QList<Worker*> workers;QThread *thread;
};#endif // MAINWINDOW_H

总结:

子类化QObject实现多线程的方法,其实是QT优化QThread弊端后的一种更加灵活的封装,默认支持事件循环支持槽函数,整个类都在新的线程中,唯一注意的是它的销毁过程。

这篇关于QT6之多线程——子类化QObject和子类化QThread的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/952720

相关文章

多线程解析报表

假如有这样一个需求,当我们需要解析一个Excel里多个sheet的数据时,可以考虑使用多线程,每个线程解析一个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完成。 Way1 join import java.time.LocalTime;public class Main {public static void main(String[] args) thro

Java 多线程概述

多线程技术概述   1.线程与进程 进程:内存中运行的应用程序,每个进程都拥有一个独立的内存空间。线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换、并发执行,一个进程最少有一个线程,线程实际数是在进程基础之上的进一步划分,一个进程启动之后,进程之中的若干执行路径又可以划分成若干个线程 2.线程的调度 分时调度:所有线程轮流使用CPU的使用权,平均分配时间抢占式调度

Java 多线程的基本方式

Java 多线程的基本方式 基础实现两种方式: 通过实现Callable 接口方式(可得到返回值):

JAVA- 多线程

一,多线程的概念 1.并行与并发 并行:多个任务在同一时刻在cpu 上同时执行并发:多个任务在同一时刻在cpu 上交替执行 2.进程与线程 进程:就是操作系统中正在运行的一个应用程序。所以进程也就是“正在进行的程序”。(Windows系统中,我们可以在任务管理器中看 到进程) 线程:是程序运行的基本执行单元。当操作系统执行一个程序时, 会在系统中建立一个进程,该进程必须至少建立一个线

多线程篇(阻塞队列- LinkedBlockingDeque)(持续更新迭代)

目录 一、LinkedBlockingDeque是什么 二、核心属性详解 三、核心方法详解 addFirst(E e) offerFirst(E e) putFirst(E e) removeFirst() pollFirst() takeFirst() 其他 四、总结 一、LinkedBlockingDeque是什么 首先queue是一种数据结构,一个集合中

多线程篇(阻塞队列- LinkedBlockingQueue)(持续更新迭代)

目录 一、基本概要 1. 构造函数 2. 内部成员 二、非阻塞式添加元素:add、offer方法原理 offer的实现 enqueue入队操作 signalNotEmpty唤醒 删除线程(如消费者线程) 为什么要判断if (c == 0)时才去唤醒消费线程呢? 三、阻塞式添加元素:put 方法原理 图解:put线程的阻塞过程 四、非阻塞式移除:poll方法原理 dequ

spring笔记 多线程的支持

spring的工作机制 136  属性编辑器 140 spring事件的体系结构 168 Bean间的关系 109 继承 依赖 引用     Bean的继承          1 为了简化初始化的属性注入;          2 子Bean和父Bean相同的属性值,使用子Bean的     Bean的依赖 Srping控制相互依赖的Bean之间,属性注入的顺序,防止出错  depend-on

【编程底层思考】详解Java的JUC多线程并发编程底层组件AQS的作用及原理

Java中的AbstractQueuedSynchronizer(简称AQS)是位于java.util.concurrent.locks包中的一个核心组件,用于构建锁和其他同步器。AQS为实现依赖于FIFO(先进先出)等待队列的阻塞锁和相关同步器提供了一套高效、可扩展的框架。 一、AQS的作用 统一同步状态管理:AQS提供了一个int类型的成员变量state,用于表示同步状态。子类可以根据自己

多线程的系列文章

Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1)   Java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Java多线程学习(四)等待/通知(wait/notify)机制 Java多线程学习(五)线程间通信知识点补充 Java多线程学习(六)Lock锁的使用 Java多

多线程 线程池的创建

一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的。在jdk1.5之后这一情况有了很大的改观。Jdk1.5之后加入了java.util.concurrent包,这个包中主要介绍java中线程以及线程池的使用。为我们在开发中处理线程的问题提供了非常大的帮助。 二:线程池 线程池的作用: 线程池作用就是限制系统中执行线程的数量。