Qt 线程 QThread类详解

Qt 线程中QThread的使用

在进行桌面应用程序开发的时候, 假设应用程序在某些情况下需要处理比较复杂的逻辑, 如果只有一个线程去处理,就会导致窗口卡顿,无法处理用户的相关操作。这种情况下就需要使用多线程,其中一个线程处理窗口事件,其他线程进行逻辑运算,多个线程各司其职,不仅可以提高用户体验还可以提升程序的执行效率

在 qt 中使用了多线程,有些事项是需要额外注意的:

  • 默认的线程在Qt中称之为窗口线程,也叫主线程,负责窗口事件处理或者窗口控件数据的更新
  • 子线程负责后台的业务逻辑处理,子线程中不能对窗口对象做任何操作,这些事情需要交给窗口线程处理
  • 主线程和子线程之间如果要进行数据的传递,需要使用Qt中的信号槽机制

1. 线程类 QThread

Qt 中提供了一个线程类,通过这个类就可以创建子线程了,Qt 中一共提供了两种创建子线程的方式,后边会依次介绍其使用方式。先来看一下这个类中提供的一些常用 API 函数:

// Qt中的线程可以设置优先级
// 得到当前线程的优先级
Priority QThread::priority() const;
void QThread::setPriority(Priority priority);
优先级:
    QThread::IdlePriority         --> 最低的优先级
    QThread::LowestPriority
    QThread::LowPriority
    QThread::NormalPriority
    QThread::HighPriority
    QThread::HighestPriority
    QThread::TimeCriticalPriority --> 最高的优先级
    QThread::InheritPriority      --> 子线程和其父线程的优先级相同, 默认是这个


start(): 启动线程,使线程进入运行状态,调用线程的run()方法。
run(): 线程的执行函数,需要在该函数中编写线程所需执行的任务。
quit(): 终止线程的事件循环,在下一个事件处理周期结束时退出线程。
wait(): 阻塞当前线程,直到线程执行完成或超时。
finished(): 在线程执行完成时发出信号。
terminate(): 强制终止线程的执行,不推荐使用,可能导致资源泄漏和未定义行为。
isRunning(): 判断线程是否正在运行。
currentThreadId(): 返回当前线程的ID。
yieldCurrentThread(): 释放当前线程的时间片,允许其他线程执行。
msleep(): 让当前线程休眠指定的毫秒数。


2 信号槽

// 和调用 exit() 效果是一样的
// 调用这个函数之后, 再调用 wait() 函数
[slot] void QThread::quit();
// 启动子线程
[slot] void QThread::start(Priority priority = InheritPriority);
// 线程退出, 可能是会马上终止线程, 一般情况下不使用这个函数
[slot] void QThread::terminate();

// 线程中执行的任务完成了, 发出该信号
// 任务函数中的处理逻辑执行完毕了
[signal] void QThread::finished();
// 开始工作之前发出这个信号, 一般不使用
[signal] void QThread::started();

3 静态函数

// 返回一个指向管理当前执行线程的QThread的指针
[static] QThread *QThread::currentThread();
// 返回可以在系统上运行的理想线程数 == 和当前电脑的 CPU 核心数相同
[static] int QThread::idealThreadCount();
// 线程休眠函数
[static] void QThread::msleep(unsigned long msecs);	// 单位: 毫秒
[static] void QThread::sleep(unsigned long secs);	// 单位: 秒
[static] void QThread::usleep(unsigned long usecs);	// 单位: 微秒

4 重写函数

// 子线程要处理什么任务, 需要写到 run() 中
[virtual protected] void QThread::run();

2种线程使用方式

使用 QThread 时的一个常见误区。

  1. QThread 实例存在于实例化它的旧线程中:

    • 当您创建一个 QThread 对象时,该对象会存在于创建它的线程中,而不是在新创建的线程中。
    • 这意味着,您调用 QThread 对象的方法和槽时,实际上是在创建该对象的线程中执行的,而不是在新创建的工作线程中。
  2. 在新线程中调用槽:

    • 如果您希望在新创建的工作线程中执行某些操作,比如调用槽函数,就不能直接将这些槽函数定义在 QThread 子类中。
    • 因为 QThread 子类的方法和槽都会在创建该对象的线程中执行,而不是在工作线程中执行。
  3. 使用工作对象方法:

    • 为了在新创建的工作线程中执行操作,您需要定义一个独立的工作对象类,并将所有的工作逻辑封装在该类中。
    • 在工作线程中,您可以创建这个工作对象的实例,并在该实例上调用方法来执行工作。

使用 QThread 时需要注意区分线程的概念。QThread 对象本身存在于创建它的线程中,而不是在新创建的工作线程中。如果您希望在工作线程中执行操作,需要使用独立的工作对象,而不是直接在 QThread 子类中实现。这样可以确保在正确的线程中执行您的工作逻辑。 

简化:在这里先记住

1.new QThread 是在当前代码位置创建出来的

2.线程调用槽函数是有坑点的。

3.最好使用工作对象方法

线程和工作逻辑

  • 主线程和子线程执行的顺序不确定,偶尔主线程在前,偶尔子线程在前。
  • 只有run()函数运行在子线程中,run调用子函数则在子线程调用子函数。
class WorkerThread : public QThread
{
    Q_OBJECT

public:
    void run() override  //子线程工作函数
    {
        // 在新线程中执行耗时任务
        for (int i = 0; i < 5; ++i)
        {
            qDebug() << "Worker thread doing work..." << i;
            sleep(1);
        }
        emit finished();
    }

signals:
    void finished();
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 创建并启动工作线程
    WorkerThread* workerThread = new WorkerThread;
    QObject::connect(workerThread, &WorkerThread::finished, workerThread, &WorkerThread::deleteLater);
    workerThread->start();



     // 等待任务完成
    workerThread->wait();

    // 停止并退出事件循环
    a.exit();
    return a.exec();
}

这个例子演示了如何在一个全新的线程中执行耗时的工作任务。通过继承 QThread 并重写 run() 方法,我们可以在新线程中运行自定义的工作逻辑。这种方式与下列的例子有所不同,下列的例子是在工作对象中执行任务,而不是在 QThread 子类中。

线程和工作对象

  • 槽函数无论是线程的信号触发还是自定义信号触发,槽函数都在新线程里运行。
  • 成员函数和主函数运行在主线程当中。
#include <QDebug>
#include <QThread>

class Worker : public QObject
{
    Q_OBJECT

public:
    Worker()
    {
    }

public slots:
    void doWork()
    {
        // 执行耗时的计算任务
        for (int i = 0; i < 5; ++i)
        {
            qDebug() << "Worker thread doing work..." << i;
            QThread::sleep(1);
        }

        // 计算完成后,发射 finished 信号
        emit finished();
    }

signals:
    void finished();
};


int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 创建工作线程
    QThread* workerThread = new QThread;

    // 创建工作对象实例,并将其移动到工作线程中
    Worker* worker = new Worker;
    worker->moveToThread(workerThread);//将其移动到工作线程中。这确保了 Worker 对象的所有操作都在工作线程中进行

    // 连接信号和槽
    QObject::connect(workerThread, &QThread::started, worker, &Worker::doWork);
    QObject::connect(worker, &Worker::finished, workerThread, &QThread::quit);
    //即使 main() 函数执行完毕,主线程退出,QThread 对象也不会立即被释放,而是会等待工作线程完成后再被删除。
    QObject::connect(worker, &Worker::finished, worker, &Worker::deleteLater);
    QObject::connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater);

    // 启动工作线程
    workerThread->start();

    return a.exec();
}

在这个例子中,QThread 对象代表了实际的工作线程,而 Worker 对象包含了实际的工作逻辑。这样,我们就可以在工作线程中执行耗时的计算任务,而不会阻塞主线程。当任务完成时,Worker 对象会发射 finished 信号,从而触发工作线程退出和对象清理。 

代码运行示例

重写线程类:work.h

#ifndef WORK_H
#define WORK_H

#include<QDebug>
#include<QThread>
#include<QMutex>
#include<QSemaphore>
class work : public QThread
{
    Q_OBJECT

public:
    work();
    virtual ~work(); // 添加虚拟析构函数


public:
    void run() override;

    void stopAndWait() {  //完美退出
        m_running = false;
        wait();     //等待任务完成
        exit();  //停止并退出事件循环
        qDebug() << "stopAndWait";

        deleteLater(); //安全地删除 QObject 及其子类对象 异步地删除对象,而不是立即删除 注意:该函数是线程安全,多次调用该函数是安全的;
    }

private:
    bool m_running; //完美退出
    static uint16_t index;
};

#endif // WORK_H


#include "work.h"

uint16_t work::index = 1;

work::work()
{
    // 构造函数实现
    m_running=true;
}

work::~work()
{
    // 虚拟析构函数实现
}
void work::run() {
    // 执行耗时工作
    while (m_running) {
        qDebug() << "Worker thread doing run..." << this->index++;
        sleep(1);
    }
    qDebug() << "Worker thread exiting.";
}

工作对象类

workthread.h

#ifndef WORKTHREAD2_H
#define WORKTHREAD2_H
#include<QDebug>
#include<QThread>


class workthread2 : public QObject
{
    Q_OBJECT
public:
    workthread2()=default;
    ~workthread2()=default;
public:
    enum class ThreadState { Idle, Running, Finished };
    Q_ENUM(ThreadState)

signals:
    void finished();

public slots:
    void doWork()
    {
        // 执行耗时的计算任务
        for (int i = 0; i < 5; ++i)
        {
            qDebug() << "Worker thread doing work..." << i;
            QThread::sleep(1);
        }

        // 计算完成后,发射 finished 信号
        emit finished();
    }


};

#endif // WORKTHREAD2_H

main函数

#include <QCoreApplication>

#include"work.h"
#include<workthread2.h>
#include<QTimer>
void qtThread_text(){
    //第一种 继承重写run方法
    QVector<work*> workerThreads;
    // 创建并启动工作线程
    for(int i=0;i<3;i++)
    {
        workerThreads.append(new work());
    }
    for(int i=0;i<3;i++)
    {
        workerThreads.at(i)->start();
    }


    // 等待一段时间后,通知线程退出
    QThread::sleep(1);
    for(work* data :workerThreads){
        data->stopAndWait();
        //data->deleteLater();
    }
    //    for(int i=0;i<3;i++)
    //    {
    //        workerthread[i]->wait();
    //        workerthread[i]->terminate(); //发送线程终止信号
    //    }


    //第2种 使用对象模式 注意此模式是在当前线程运行
    QThread* newthread= new QThread;
    QThread* newthread2= new QThread;
    // 启动工作线程 一个对象只能隶属于一个线程


    workthread2* work2 = new workthread2;
    workthread2* work2_2 = new workthread2;
    work2->moveToThread(newthread);
    work2_2->moveToThread(newthread2);
    // 启动工作线程
   newthread->start();
   newthread2->start();

   // 连接信号和槽
    QObject::connect(newthread, &QThread::started, work2, &workthread2::doWork);
    QObject::connect(work2, &workthread2::finished, newthread, &QThread::quit);
    QObject::connect(work2, &workthread2::finished, work2, &workthread2::deleteLater);
    QObject::connect(newthread, &QThread::finished, newthread, &QThread::deleteLater);

    QObject::connect(newthread2, &QThread::started, work2_2, &workthread2::doWork);
    QObject::connect(work2_2, &workthread2::finished, newthread2, &QThread::quit);
    QObject::connect(work2_2, &workthread2::finished, work2_2, &workthread2::deleteLater);
    QObject::connect(newthread2, &QThread::finished, newthread2, &QThread::deleteLater);

    newthread->start();
    newthread2->start();
/*
    newthread->wait();
    newthread->exit();
    当 newthread->wait() 被调用时,它会阻塞当前线程(即主线程)直到 newthread 线程退出。然后 newthread->exit() 被调用,停止并退出了事件循环。
    qDebug() << "a.exec()"; 这行代码就不会被执行。
*/

    // 等待线程完成并退出事件循环
   newthread->wait();
   newthread2->wait();

   newthread->exit();
   newthread2->exit();
}
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    qtThread_text();

    // 手动调用 a.exec()
    qDebug() << "a.exec()";
    return a.exec(); //开启Qt 的事件循环
}

运行结果

总结

上述提到的两种使用 QThread 的方式有以下几点主要区别: (第一种重写 第二种movetoThread)

  1. 线程的创建方式

  2. ***线程与工作对象的关系:

    • 在第一种方式中,线程和工作逻辑是耦合在一起的,因为工作逻辑直接在 QThread 的子类中实现。
    • 在第二种方式中,线程和工作对象是解耦的,工作逻辑被封装在独立的 Worker 对象中。
  3. ***信号槽的执行位置:
    • move版本槽函数无论是线程的信号触发还是自定义信号触发,槽函数都在新线程里运行。
    • 重写版本信号槽是在主线程执行。
  4. 线程安全性:

    • 在第二种方式中,由于工作对象是在工作线程中运行的,因此不需要担心线程安全性问题。
    • 在第一种方式中,如果在 QThread 的子类中访问了共享资源,就需要特别注意线程安全性。
  5. 灵活性和可重用性:

    重写灵活性和可重用性低,move版本低耦合
  6. 错误处理:

    • 在第二种方式中,可以更容易地在工作对象中捕获和处理错误,因为工作逻辑被封装在了独立的对象中。
    • 在第一种方式中,错误处理可能更加困难,因为工作逻辑和线程紧耦合在一起。

总的来说,主要区别在于代码结构和设计模式,而不是线程的创建方式。无论采用哪种方式,都是在主线程中创建和启动了工作线程。由于4.8更新了movetothread版本,既然是更新,说明这种方法更好,建议大家在使用时采用movetothread版本

参考文献:

Qt 线程中QThread的使用_qt qthread-CSDN博客

QThread 类 | Qt 核心 5.15.17 --- QThread Class | Qt Core 5.15.17

一文搞定之Qt多线程(QThread、moveToThread)_qthread movetothread-CSDN博客

最后附上源代码链接
对您有帮助的话,帮忙点个star

36-qthread-qmutex-qsemaphore · jbjnb/Qt demo - 码云 - 开源中国 (gitee.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/786439.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

亚马逊云科技EC2简明教程

&#x1f4a1; 完全适用于新手操作的Amazon EC2引导教程 简述 在亚马逊云科技中&#xff0c;存在多种计算服务&#xff0c;在此&#xff0c;我们将会着重讨论Amazon EC2(以下简称EC2)&#xff0c;EC2作为亚马逊云科技的明星产品、核心产品&#xff0c;是大多数开发者和企业用…

基于JAVA+SpringBoot+Vue的自动阅卷分析系统

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 在当前教育评估体系中…

网络安全高级工具软件100套

1、 Nessus&#xff1a;最好的UNIX漏洞扫描工具 Nessus 是最好的免费网络漏洞扫描器&#xff0c;它可以运行于几乎所有的UNIX平台之上。它不止永久升级&#xff0c;还免费提供多达11000种插件&#xff08;但需要注册并接受EULA-acceptance–终端用户授权协议&#xff09;。 它…

【面试八股总结】面向对象三大特性、虚函数、纯虚函数、虚继承

参考资料&#xff1a;阿秀 一、面向对象三大特性 封装&#xff1a;将数据和代码捆绑在一起&#xff0c;避免外界干扰和不确定性访问 继承&#xff1a;让某种类型对象获得另一个类型对象的属性和方法 多态&#xff1a;同一种事务表现出不同事务的能力&#xff0c;即&#xf…

算法小练之 位运算基础

前言 今天正式走入&#xff0c;位运算这个章节&#xff0c;关于这一部分我会先介绍几个重要的知识点&#xff0c;然后再根据几个力扣上的题来讲解。 了解6种位操作 总所周知&#xff0c;变量在计算机中都是二进制存储的&#xff0c;比如一个变量int a 1&#xff1b; 它的存…

Halcon 模糊圆边的找圆案例

Halcon 模糊圆边的找圆案例 基本思路 1.将图像转成灰度图像 2.再观察要找到的区域的灰度值变化&#xff0c;找到前景与背景的具体数值。 3.根据找到的前景与背景的具体数值&#xff0c;增强图像对比度。&#xff08;使图像变成黑白图片&#xff09; 4.使用灰度直图工具进行阈值…

gRPC 接口测试最佳实践

gRPC 是由谷歌开发的现代开源高性能 RPC 远程过程调用框架&#xff0c;由于采用了HTTP/2 作为底层传输协议&#xff0c;它特别适用于高性能应用场景。gRPC 在视频流传输等大规模数据传输场景以及密集的服务间通讯的微服务架构中表现出色。 数据交换使用轻量级的 Protobuf 序列…

18.按键消抖模块设计(使用状态机,独热码编码)

&#xff08;1&#xff09;设计意义&#xff1a;按键消抖主要针对的时机械弹性开关&#xff0c;当机械触点断开、闭合时&#xff0c;由于机械触点的弹性作用&#xff0c;一个按键开关在闭合时不会马上稳定地接通&#xff0c;在断开时也不会一下子就断开。因而在闭合以及断开的瞬…

Jmeter-接口测试-GET请求

简介 Jmeter 是 apache 公司基于 java 开发的一款开源压力测试工具&#xff0c;体积小&#xff0c;功能全&#xff0c;使用方便&#xff0c;是一个比较轻量级的测试工具&#xff0c;使用起来非常简 单。因为 jmeter 是 java 开发的&#xff0c;所以运行的时候必须先要安装 jdk…

数据结构——Trie

题目&#xff1a; 维护一个字符串集合&#xff0c;支持两种操作&#xff1a; I x 向集合中插入一个字符串 x&#x1d465;&#xff1b;Q x 询问一个字符串在集合中出现了多少次。 共有 N&#x1d441; 个操作&#xff0c;所有输入的字符串总长度不超过 10^5&#xff0c;字符串仅…

(HAL)stm32f407+freertos通过usb驱动移远4G模块-EC600U

概述 本篇文章主要介绍: 如何使用STM32CubeMX创建stm32F407+freertos+usb host的基础工程。USB-HOST-CDC驱动运行过程。如何根据4G模块的具体信息修改usb相关代码。MCU如何通过usb与4G模块通信,收发数据。调试过程中遇到的问题以及解决办法。 整个过程中在网上搜罗了很多参考…

Test-Time Adaptation via Conjugate Pseudo-labels--论文笔记

论文笔记 资料 1.代码地址 https://github.com/locuslab/tta_conjugate 2.论文地址 https://arxiv.org/abs/2207.09640 3.数据集地址 论文摘要的翻译 测试时间适应(TTA)指的是使神经网络适应分布变化&#xff0c;在测试时间仅访问来自新领域的未标记测试样本。以前的TT…

STM32(二):STM32工作原理

这里写目录标题 0、参考1、寄存器和存储器基本概念&#xff08;1&#xff09;基本概念&#xff08;2&#xff09;主要区别&#xff08;3&#xff09;联系&#xff08;4&#xff09;实际应用中的案例&#xff08;5&#xff09;总结&#xff08;6&#xff09;一些名词解释 2、STM…

实时监测、智能预警:电缆光纤测温系统|原理、应用与前景

实时监测、智能预警&#xff1a;电缆光纤测温系统|原理、应用与前景 电缆光纤测温系统&#xff0c;作为现代电力系统中不可或缺的一部分&#xff0c;以其独特的优势在电缆安全监控领域发挥着日益重要的作用。该系统利用光纤传感技术&#xff0c;实时监测电缆的运行温度&#x…

Qt常用基础控件总结—带边框的部件(QFrame和QLabel)

带边框的部件 框架控件QFrame类 QFrame类介绍 QFrame 类是带有边框的部件的基类,带边框部件的特点是有一个明显的边框,QFrame类就是用来实现边框的不同效果的(把这种效果称为边框样式),所有继承自 QFrame 的子类都可以使用 QFrame 类实现的效果。 部件通常是矩形的(其他…

Kithara和OpenCV (一)

Kithara使用 OpenCV 目录 Kithara使用 OpenCV简介需求和支持的环境构建 OpenCV 库使用 CMake 进行配置以与 Kithara 一起工作 使用 OpenCV 库设置项目运行 OpenCV 代码图像采集和 OpenCV自动并行化限制和局限性1.系统建议2.实时限制3.不支持的功能和缺失的功能4.显示 OpenCV 对…

【Perforce】QAC-分析时如何不应用某些规则

【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 解决扫描项目时如何不应用某些规则进行分析。 2、 问题场景 对于一些建议性的MISRA规则&#xff0c;不想用于项目扫描&#xff0c;如何处理&#xff1f; 3、软硬件环境 1、软件版本&#xff1a;HelixQAC23.04 2…

中国科学院院士丁汉:人形机器人——机器人与人工智能结合的爆发点

工业制造是国民经济的重要支柱&#xff0c;是实现发展升级的国之重器。早在 2002 年&#xff0c;党的十六大就曾提出&#xff0c;坚持以信息化带动工业化&#xff0c;以工业化促进信息化&#xff0c;走出一条科技含量高、经济效益好、资源消耗低、环境污染少、人力资源优势得到…

24年,计算机仍然是最热门的专业?!

大家好&#xff0c;我是程序员鱼皮。最近很多高考完的朋友开始进入了填志愿选专业的时期。出于好奇&#xff0c;我也在网上了解了一下今年的热门专业和就业情况&#xff0c;结果并没有出乎我的意料&#xff0c;对于很多省份&#xff0c;计算机科学与技术依然是最热门的专业&…

fastadmin 各种开发技巧,问题总合集,持续跟新中....

使用 搜索的使用 自定义按钮 需改后的代码 {field: operate, title: __(Operate), table: table,buttons: [{name: detail, text: 详情, title: 详情, icon: fa fa-list, classname: btn btn-xs btn-primary btn-dialog, url: version/detail},{name: edit, text: 编辑我, …