Qt 线程 QThread类详解

news2024/9/22 13:41:55

Qt 线程中QThread的使用

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

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

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

1. 线程类 QThread

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

// QThread 类常用 API
// 构造函数
QThread::QThread(QObject *parent = Q_NULLPTR);
// 判断线程中的任务是不是处理完毕了
bool QThread::isFinished() const;
// 判断子线程是不是在执行任务
bool QThread::isRunning() const;

// 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      --> 子线程和其父线程的优先级相同, 默认是这个
// 退出线程, 停止底层的事件循环
// 退出线程的工作函数
void QThread::exit(int returnCode = 0);
// 调用线程退出函数之后, 线程不会马上退出因为当前任务有可能还没有完成, 调回用这个函数是
// 等待任务完成, 然后退出线程, 一般情况下会在 exit() 后边调用这个函数
bool QThread::wait(unsigned long time = ULONG_MAX);

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 子类中实现。这样可以确保在正确的线程中执行您的工作逻辑。 

线程和工作逻辑

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. 线程的创建方式:

    • 第一种方式是继承 QThread 类,并在 run() 方法中编写线程的工作逻辑。
    • 第二种方式是创建一个 QThread 对象,并使用 moveToThread() 将一个独立的工作对象移动到该线程中。
  2. ***线程与工作对象的关系:

    • 在第一种方式中,线程和工作逻辑是耦合在一起的,因为工作逻辑直接在 QThread 的子类中实现。
    • 在第二种方式中,线程和工作对象是解耦的,工作逻辑被封装在独立的 Worker 对象中。
  3. 灵活性和可重用性:

    • 第二种方式更加灵活和可重用。因为工作对象可以独立于线程而存在,可以在不同的线程中复用。
    • 第一种方式的 QThread 子类更难复用,因为工作逻辑与线程紧耦合。
  4. 线程安全性:

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

    • 在第二种方式中,可以更容易地在工作对象中捕获和处理错误,因为工作逻辑被封装在了独立的对象中。
    • 在第一种方式中,错误处理可能更加困难,因为工作逻辑和线程紧耦合在一起。
  6. ***信号槽的执行位置:
    方式 1 中,由于 WorkerThread 对象本身就在新线程中,所以其信号槽都在新线程中执行。
    方式 2 中,Worker 对象的槽函数 doWork() 是在新线程中执行的,而 Worker 对象本身以及其他信号槽仍然在创建 Worker 对象的主线程中执行

总的来说,主要区别在于代码结构和设计模式,而不是线程的创建方式。无论采用哪种方式,都是在主线程中创建和启动了工作线程。
 

参考文献:

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

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

盲盒小程序开发:互联网下的的盲盒发展

近些年来&#xff0c;盲盒行业发展的非常迅速&#xff0c;盲盒的不确定性吸引了无数玩家&#xff0c;盲盒的市场规模逐渐扩大&#xff0c;盲盒品牌也在不断出现&#xff0c;为盲盒消费者带来更多的新鲜体验&#xff0c; 随着互联网小程序的的快速发展&#xff0c;盲盒小程序为…

告别‘找文件’大战,可道云teamOS分区管理,文件秒定位

在数字化时代&#xff0c;数据已经成为企业最宝贵的资产之一。 如何高效、安全地管理这些数据&#xff0c;成为了每一个企业必须面对的问题。 企业网盘作为一种便捷的数据存储和共享工具&#xff0c;已经成为了企业日常运营中不可或缺的一部分。 然而&#xff0c;传统的网盘管…

人员定位系统于不同场景的实际应用

人员定位系统的应用&#xff0c;尽管还没有做到大范围的普及&#xff0c;但是这一系统在不同企业&#xff0c;不同单位的实际应用效果还是很好的&#xff0c;所以人员定位系统也应用于不同场景当中了&#xff0c;那么&#xff0c;本文就来讲讲这一系统在不同场景的实际应用。 人…

汽车信息安全--欧盟汽车法规

目录 General regulation 信息安全法规 R155《网络安全及网络安全管理系统》解析 R156《软件升级与软件升级管理系统》解析 General regulation 欧洲的汽车行业受到一系列法律法规的约束&#xff0c;包括 各个方面包括&#xff1a; 1.安全要求&#xff1a;《通用安全条例&a…

【Qt】对话框

1、自定义对话框并赋予ui界面&#xff0c;用按钮呼出 https://www.bilibili.com/video/BV1rK411A7qi/?spm_id_from333.999.0.0&vd_sourcefd6555f02904e7fa85526a2ff4b8b66e 新建 - 文件和类 - Qt - Qt设计师界面类在原来的父窗口cpp文件中初始化新窗口并调用exec显示模态…

什么软件能够监控电脑?五大好用监控电脑软件推荐

在当今的企业管理中&#xff0c;电脑监控软件已经成为必不可少的工具。它们不仅能提升员工的工作效率&#xff0c;还能保护企业的核心数据&#xff0c;防止信息泄露。以下将介绍几款市场上备受好评的电脑监控软件&#xff0c;特别是固信软&#xff0c;帮助企业找到最适合的解决…

软件测试下的AI之路(5)

😏作者简介:博主是一位测试管理者,同时也是一名对外企业兼职讲师。 📡主页地址:【Austin_zhai】 🙆目的与景愿:旨在于能帮助更多的测试行业人员提升软硬技能,分享行业相关最新信息。 💎声明:博主日常工作较为繁忙,文章会不定期更新,各类行业或职场问题欢迎大家…

掌握MySQL基础命令:数据更新操作详细操作(数据的增删改)

MySQL数据修改是指使用SQL语句&#xff08;如UPDATE、INSERT、DELETE&#xff09;对数据库表中的数据进行更改、添加或删除的操作&#xff0c;常见的操作包括更新表中的记录、插入新记录以及删除现有记录 。 一、数据插入 1插入完整的数据记录 2插入非完整的数据记录 3插入多…

SQL 之 concat_ws和concat的区别

concat_ws和concat都是用于连接字符串的函数&#xff0c;但它们在使用上有一些区别&#xff1a; 一、concat、concat_ws函数格式&#xff1a; concat格式&#xff1a; concat&#xff08;参数1,参数2,…参数n&#xff09;&#xff0c;如果要加’分隔符’直接写在 各参数中间就…

【知网CNKI-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

【Git基本操作】创建本地仓库 | 配置本地仓库 | 认识工作区、暂存区、版本库、对象库 | add和commit操作

目录 1.创建Git本地仓库 1.1创建仓库 1.2创建和初始化Git本地仓库 1.3查看隐藏目录.git 2.配置本地仓库 2.1新增配置 2.2删除重置配置 2.3查看配置选项 2.4全局范围的新增和删除配置 3.工作区、暂存区、版本库、对象库 ​4.add操作和commit操作 4.1add操作 4.2com…

AI 大模型系统实战

AI 大模型是什么&#xff1f; 维基百科对基础模型的定义是这样的&#xff0c;基础模型是一种大型机器学习模型&#xff0c;通常在大量数据上进行大规模训练&#xff08;通过自监督学习或半监督学习&#xff09;&#xff0c;以使它可以适应各类下游任务。因此&#xff0c;它需要…

从入门到精通:Shopee,lazada,temu自养号测评成本、步骤、技巧详解

测评对于卖家来说是一种成本低回报快的推广方式&#xff0c;可以减少高额的平台广告费用&#xff0c;因此是一种很好的辅助手段&#xff0c;对商品的曝光、流量、转化和权重等方面起到了很好的辅助作用 建议还是自己精养一批账号&#xff0c;账号在自己手里比较安全可控&#…

重塑消费体验:探索绿色消费增值模式的新篇章

我是吴军&#xff0c;就职于一家在数字创新领域屡获殊荣的软件企业&#xff0c;担任高级产品策略师。今天&#xff0c;我满怀热忱&#xff0c;想与您一同揭开一种前沿且极具吸引力的商业模式面纱——那就是绿色消费增值模式&#xff0c;一个正逐步改变我们消费习惯与商业生态的…

一.1 信息就是位+上下文

hello程序的生命周期是从一个源程序&#xff08;或者说源文件&#xff09;开始的&#xff0c;即程序员通过编辑器创建并保存的文本文件&#xff0c;文件名是hello.c。源程序实际上就是一个由0和1组成的位&#xff08;又称为比特&#xff09;序列&#xff0c;8个位被组织成一组&…

平面法向的角度表示以及坐标系变换

1.根据法线计算法线的垂直角sint和法线在水平投影与x轴的夹角phi double phi atan2(normal(1) , normal(0)); // atan2(y,x), 计算法向在xy平面上的投影和x轴之间的夹角double sint asin(normal(2)); //理论上是z轴和 该法向向量之间的夹角 2.根据角度计算法线 Eigen::Vec…

AnaPico为众多工厂产线老化测试提供高效经济的微波解决方案

在电子设备的生产中&#xff0c;老化测试在整个使用寿命期间的可靠性和对声明参数的保证起着重要作用&#xff0c;尤其是在特殊应用&#xff08;国防和航天工业、电信、医药等&#xff09;方面。即使经过成功的参数和功能测试&#xff0c;在实际操作条件下使用时也有可能出现设…

集芯微电推出固定输出2V|2.5V|3V|3.3V|4V|4.5V|5V_输入最大16V_10 ppm/°C低噪声低漂移高精度基准电压源

1特征 •低温漂移&#xff1a;3 ppm/C&#xff08;典型&#xff09; •高精度&#xff1a;最大0.1% •低噪声&#xff1a;7.5μVPP/V •低IQ&#xff1a;2 mA&#xff08;典型&#xff09; •工作温度范围&#xff1a;-40C至125C •高输出电流&#xff1a;10 mA •微型包…

《基于 Kafka + Flink + ES 实现危急值处理措施推荐和范围校准》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; 近期刚转战 CSDN&#xff0c;会严格把控文章质量&#xff0c;绝不滥竽充数&#xff0c;欢迎多多交流。&am…

数字签密:信息安全的新防线

随着互联网的普及和数字技术的飞速发展&#xff0c;信息安全问题日益凸显。在这个背景下&#xff0c;数字签密技术应运而生&#xff0c;为保护信息安全提供了新的解决方案。本文将介绍数字签密的概念、原理及应用&#xff0c;探讨其在信息安全领域的重要性。 数字签密的概念 …