Qt中多线程的使用

news2024/9/22 3:50:24

前言

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

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

1. 线程类 QThread

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

1.1 常用共用成员函数

// 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);

1.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();

1.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);	// 单位: 微秒

1.4 任务处理函数

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

这个run()是一个虚函数,如果想让创建的子线程执行某个任务,需要写一个子类让其继承QThread,并且在子类中重写父类的run()方法,函数体就是对应的任务处理流程。另外,这个函数是一个受保护的成员函数,不能够在类的外部调用,如果想要让线程执行这个函数中的业务流程,需要通过当前线程对象调用槽函数start()启动子线程,当子线程被启动,这个run()函数也就在线程内部被调用了。

2. 使用方式1

2.1 操作步骤

Qt中提供的多线程的第一种使用方式的特点是: 简单。操作步骤如下:

  1. 需要创建一个线程类的子类,让其继承QT中的线程类 QThread,比如:
class MyThread:public QThread
{
    ......
}
  1. 重写父类的 run() 方法,在该函数内部编写子线程要处理的具体的业务流程
class MyThread:public QThread
{
    ......
 protected:
    void run()
    {
        ........
    }
}
  1. 在主线程中创建子线程对象,new 一个就可以了
MyThread * subThread = new MyThread;
  1. 启动子线程, 调用 start() 方法
subThread->start();

不能在类的外部调用run() 方法启动子线程,在外部调用start()相当于让run()开始运行

当子线程别创建出来之后,父子线程之间的通信可以通过信号槽的方式,注意事项:

  • 在Qt中在子线程中不要操作程序中的窗口类型对象, 不允许, 如果操作了程序就挂了。
  • 只有主线程才能操作程序中的窗口对象, 默认的线程就是主线程, 自己创建的就是子线程

2.2 示例代码

举一个简单的数数的例子,假如只有一个线程,让其一直数数,会发现数字并不会在窗口中时时更新,并且这时候如果用户使用鼠标拖动窗口,就会出现无响应的情况,使用多线程就不会出现这样的现象了。

在下面的窗口中,点击按钮开始在子线程中数数,让后通过信号槽机制将数据传递给UI线程,通过UI线程将数据更新到窗口中。
在这里插入图片描述
mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>

class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);

protected:
    void run();

signals:
    // 自定义信号, 传递数据
    void curNumber(int num);

public slots:
};

#endif // MYTHREAD_H

mythread.cpp

#include "mythread.h"
#include <QDebug>

MyThread::MyThread(QObject *parent) : QThread(parent)
{

}

void MyThread::run()
{
    qDebug() << "当前线程对象的地址: " << QThread::currentThread();

    int num = 0;
    while(1)
    {
        emit curNumber(num++);
        if(num == 10000000)
        {
            break;
        }
        QThread::usleep(1);
    }
    qDebug() << "run() 执行完毕, 子线程退出...";
}

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "mythread.h"
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    qDebug() << "主线程对象地址:  " << QThread::currentThread();
    // 创建子线程
    MyThread* subThread = new MyThread;

    connect(subThread, &MyThread::curNumber, this, [=](int num)
    {
        ui->label->setNum(num);
    });

    connect(ui->startBtn, &QPushButton::clicked, this, [=]()
    {
        // 启动子线程
        subThread->start();
    });
}

MainWindow::~MainWindow()
{
    delete ui;
}

这种在程序中添加子线程的方式是非常简单的,但是也有弊端,假设要在一个子线程中处理多个任务,所有的处理逻辑都需要写到run()函数中,这样该函数中的处理逻辑就会变得非常混乱,不太容易维护。

3. 使用方式2

3.1 操作步骤

Qt提供的第二种线程的创建方式弥补了第一种方式的缺点,用起来更加灵活,但是这种方式写起来会相对复杂一些,其具体操作步骤如下:

  1. 创建一个新的类,让这个类从QObject派生
class MyWork:public QObject
{
    .......
}
  1. 在这个类中添加一个公共的成员函数,函数体就是我们要子线程中执行的业务逻辑
class MyWork:public QObject
{
public:
    .......
    // 函数名自己指定, 叫什么都可以, 参数可以根据实际需求添加
    void working();
}
  1. 在主线程中创建一个QThread对象, 这就是子线程的对象

QThread* sub = new QThread;
  1. 在主线程中创建工作的类对象(千万不要指定给创建的对象指定父对象)
MyWork* work = new MyWork(this);    // error
MyWork* work = new MyWork;          // ok
  1. 将MyWork对象移动到创建的子线程对象中, 需要调用QObject类提供的moveToThread()方法
// void QObject::moveToThread(QThread *targetThread);
// 如果给work指定了父对象, 这个函数调用就失败了
// 提示: QObject::moveToThread: Cannot move objects with a parent
work->moveToThread(sub);	// 移动到子线程中工作
  1. 启动子线程,调用 start(), 这时候线程启动了, 但是移动到线程中的对象并没有工作
  2. 调用MyWork类对象的工作函数,让这个函数开始执行,这时候是在移动到的那个子线程中运行的.

3.2 示例代码

假设函数处理上面在程序中数数的这个需求,具体的处理代码如下:
mywork.h

#ifndef MYWORK_H
#define MYWORK_H

#include <QObject>

class MyWork : public QObject
{
    Q_OBJECT
public:
    explicit MyWork(QObject *parent = nullptr);

    // 工作函数
    void working();

signals:
    void curNumber(int num);

public slots:
};

#endif // MYWORK_H

mywork.cpp

#include "mywork.h"
#include <QDebug>
#include <QThread>

MyWork::MyWork(QObject *parent) : QObject(parent)
{

}

void MyWork::working()
{
    qDebug() << "当前线程对象的地址: " << QThread::currentThread();

    int num = 0;
    while(1)
    {
        emit curNumber(num++);
        if(num == 10000000)
        {
            break;
        }
        QThread::usleep(1);
    }
    qDebug() << "run() 执行完毕, 子线程退出...";
}

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
#include "mywork.h"
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    qDebug() << "主线程对象的地址: " << QThread::currentThread();

    // 创建线程对象
    QThread* sub = new QThread;
    // 创建工作的类对象
    // 千万不要指定给创建的对象指定父对象
    // 如果指定了: QObject::moveToThread: Cannot move objects with a parent
    MyWork* work = new MyWork;
    // 将工作的类对象移动到创建的子线程对象中
    work->moveToThread(sub);
    // 启动线程
    sub->start();
    // 让工作的对象开始工作, 点击开始按钮, 开始工作
    connect(ui->startBtn, &QPushButton::clicked, work, &MyWork::working);
    // 显示数据
    connect(work, &MyWork::curNumber, this, [=](int num)
    {
        ui->label->setNum(num);
    });
}

MainWindow::~MainWindow()
{
    delete ui;
}

使用这种多线程方式,假设有多个不相关的业务流程需要被处理,那么就可以创建多个类似于MyWork的类,将业务流程放多类的公共成员函数中,然后将这个业务类的实例对象移动到对应的子线程中moveToThread()就可以了,这样可以让编写的程序更加灵活,可读性更强,更易于维护。

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

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

相关文章

快速幂的理解、leetcode剑指 Offer 16. 数值的整数次方解析

0、引言 算法小计&#xff1a;本文阐述以下自己对于快速幂的理解&#xff0c;之前想了很久没想通。 对于求一个数字a的n次方问题&#xff1a;&#xff0c;如果直接写循环&#xff0c;从1开始每次乘以a&#xff0c;求n次即可做出答案&#xff0c;但时间复杂度是O(n)&#xff0c…

陌陌聊天数据分析 (二)

陌陌聊天数据分析 &#xff08;二&#xff09; &#x1f3c6;FineBI构建连接 陌陌聊天数据分析 &#xff08;一&#xff09; 新建连接 配置连接 测试连接并保存 &#x1f3c5;数据准备 新建分组 添加业务包 添加表 &#x1f948;构建可视化报表 &#x1f3c8;添加…

国内好用的五款低代码平台,你用的哪一款?

随着信息化技术的发展&#xff0c;软件开发领域也在不断更新迭代&#xff0c;在企业数字化升级的浪潮中&#xff0c;低代码平台的应用也越来越广泛。从业务系统到办公软件&#xff0c;从 BI分析到移动开发&#xff0c;越来越多的企业和IT团队开始使用低代码平台来构建数字化应用…

【Nginx】第六章 Nginx配置实例-动静分离

Nginx 动静分离简单来说就是把动态跟静态请求分开&#xff0c;不能理解成只是单纯的把动态页面和静态页面物理分离。严格意义上说应该是动态请求跟静态请求分开&#xff0c;可以理解成使用Nginx 处理静态页面&#xff0c;Tomcat处理动态页面。动静分离从目前实现角度来讲大致分…

超分布检测中的平衡能量正则化损失

文章目录 Balanced Energy Regularization Loss for Out-of-distribution Detection摘要本文方法Balanced Energy regularization loss 实验结果 Balanced Energy Regularization Loss for Out-of-distribution Detection 摘要 在超分布(out of distribution, OOD)检测领域&a…

express 获取阿里云 oss 文件对象列表

使用 ali-oss 包 pnpm add ali-oss -S 创建 oss 实例&#xff0c;并拉取数据 阿里云 oss 返回示例&#xff1a;

一种接口依赖关系分层方案 | 京东云技术团队

1、背景 到店商详迭代过程中&#xff0c;需要提供的对外能力越来越多&#xff0c;如预约日历、附近门店、为你推荐等。这其中不可避免会出现多个上层能力依赖同一个底层接口的场景。最初采用的方案是对外API入口进来后获取对应的能力&#xff0c;并发调用多项能力&#xff0c;…

【Vue】组件高级

文章目录 组件高级一、watch 监听器二、组件的生命周期2.1 组件运行的过程2.2 监听组件的不同时刻2.3 监听组件的更新2.4 主要生命周期函数 三、组件之间的数据共享3.1 组件关系及数据共享3.2 父子组件数据共享3.3 兄弟组件数据共享3.4 后代组件数据共享3.5 vuex 四、全局配置 …

基于Thinkphp6框架全新UI的AI网址导航系统源码

✨ 源码介绍 2023全新UI的AI网址导航系统源码&#xff0c;基于thinkphp6框架开发的 AI 网址导航是一个非常实用的工具&#xff0c;它能够帮助用户方便地浏览和管理自己喜欢的网站。 相比于其他的 AI 网址导航&#xff0c;这个项目使用了更加友好和易用的 ThinkPHP 框架进行搭建…

基于3×3耦合器的干涉型光纤传感器信号解调技术研究-Matlab代码

一、引言 光纤耦合器是一种使光信号在特殊结构的耦合区内发生耦合&#xff0c;将功率再分配的无源器件。它在光纤传感和光通信等领域有着广泛的应用&#xff0c;在耦合过程中&#xff0c;光信号的频谱成分不变&#xff0c;只是信号的光功率发生变化。1980年&#xff0c;K&…

2023双态IT北京用户大会回顾(二) | 云内外一体化智能监控构建之路

文末附有本场专题演讲视频 2023第五届双态IT北京用户大会擎创科技专场演讲回顾&#xff08;二&#xff09;&#xff1a;《云内外一体化智能监控构建之路》演讲嘉宾&#xff1a;擎创科技产品线总监 殷传旺 一、前言&#xff1a;企业运维自然推进or独立发展&#xff1f; 我们从…

WebAssembly能否取代Docker?

“如果WebAssembly&#xff08;Wasm&#xff09;在几年前出现&#xff0c;Docker可能就不会出现了。因为它是一项非常强大的跨平台技术&#xff0c;可以让我们使用不同的编程语言来编写跨平台应用程序。Docker的原始动力之一就是提供一个跨平台部署和应用的方法。” -- Solomon…

lesson6 ZIgbee网络特性抓包分析

目录 ZIgbee网络特性抓包分析 理论部分&#xff1a; 实验部分&#xff1a; 入网前相关数据帧&#xff08;仅单个设备供电&#xff09;&#xff1a; 首先以路由器和协调器为例&#xff1a;&#xff08;帧的类别从头部的Type可以看到&#xff0c;在四大帧类别中根据不同作用也…

Vue 项目build打包发布到github pages ,刷新页面首页正常打开,子路由报错404的解决方法

一、问题效果如下&#xff1a; 二、解决方法 1、切换到gh-pages分支 2、在index.html同级新增404.html文件&#xff0c;其内容与index.html一样 三、解决后的效果如下&#xff1a; 相关文章 Vue3 Vite Ts开源后台管理系统模板 基于ElementUi或AntdUI再次封装基础组件文档 基…

Java版本spring cloud 电子招标采购系统源码:营造全面规范安全的电子招投标环境,促进招投标市场健康可持续发展

营造全面规范安全的电子招投标环境&#xff0c;促进招投标市场健康可持续发展 传统采购模式面临的挑战 一、立项管理 1、招标立项申请 功能点&#xff1a;招标类项目立项申请入口&#xff0c;用户可以保存为草稿&#xff0c;提交。 2、非招标立项申请 功能点&#xff1a;非招标…

VueX用法快速回顾(简洁纯干货)

vuex基础结构 代码结构 vuex的完整结构长这样&#xff0c;其包含了state、mutations、actions、modules及getters5个部分。 import Vue from vue import Vuex from vuexVue.use(Vuex)const store new Vuex.Store({state: {},mutations: {},actions:{},modules:{},getters:{…

mysql数据库 查询出的字段值是 科学计数法数字,数字转换为易于理解的形式

目录 1 问题2 实现 1 问题 数据库查询出来的可能是这种&#xff0c;如何将数字转换为易于理解的形式&#xff1b; 2 实现

ppt文件全部删除怎么恢复?别急,这些方法能挽救

PPT文件全部删除怎么恢复&#xff1f;如果您不小心将PPT文件删除&#xff0c;不要担心&#xff0c;您可能还有机会将其恢复。在下面的文章中&#xff0c;我们将介绍一些技巧来恢复已删除的PPT文件。 第一部分&#xff1a;关于PPT文件 PPT文件是指Microsoft PowerPoint软件创建…

互联网医院开发|互联网医院系统源码技术实现

健康医疗这几年一直成为人们比较关注的问题&#xff0c;互联网医院系统通过信息化技术精简了预约挂号&#xff0c;优化了医生的诊断流程&#xff0c;通过互联网医疗&#xff0c;患者复诊或医疗咨询需求都可以在线上进行&#xff0c;这种快捷的线上问诊方式缓解一线医务人员的压…

【成都】EFDC建模方法及在地表水环境评价、水源地划分、排污口论证中实践技术应用

为了定量地描述地表水环境质量与污染排放之间的动态关系&#xff0c;EFDC、MIKE、Delft3D、Qual2K等数值模型被广泛应用在环境、水务、海洋等多个领域。Environmental Fluid Dynamics Code&#xff08;EFDC&#xff09;是一款用于模拟江河&#xff0c;湖泊&#xff0c;河口&…