首页 > 其他分享 >Qt 中多线程的使用

Qt 中多线程的使用

时间:2023-02-04 11:34:16浏览次数:41  
标签:include Qt void MyWork 线程 使用 多线程 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);

信号槽

// 和调用 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();

静态函数

// 返回一个指向管理当前执行线程的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); // 单位: 微秒

任务处理函数

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

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

使用方式 1

操作步骤

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

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

不能在类的外部调用 run () 方法启动子线程,在外部调用 start () 相当于让 run () 开始运行
当子线程别创建出来之后,父子线程之间的通信可以通过信号槽的方式,注意事项:

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

示例代码

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

在下面的窗口中,点击按钮开始在子线程中数数,让后通过信号槽机制将数据传递给 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()函数中,这样该函数中的处理逻辑就会变得非常混乱,不太容易维护。

使用方式 2

操作步骤

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

  • 创建一个新的类,让这个类从 QObject 派生
class MyWork:public QObject
{
.......
}
  • 在这个类中添加一个公共的成员函数,函数体就是我们要子线程中执行的业务逻辑。
class MyWork:public QObject
{
public:
.......
// 函数名自己指定, 叫什么都可以, 参数可以根据实际需求添加
void working();
}
  • 在主线程中创建一个 QThread 对象,这就是子线程的对象
QThread* sub = new QThread;
  • 在主线程中创建工作的类对象(千万不要指定给创建的对象指定父对象)
MyWork* work = new MyWork(this);    // error
MyWork* work = new MyWork; // ok
  • 将 MyWork 对象移动到创建的子线程对象中,需要调用 QObject 类提供的 moveToThread() 方法
// void QObject::moveToThread(QThread *targetThread);
// 如果给work指定了父对象, 这个函数调用就失败了
// 提示: QObject::moveToThread: Cannot move objects with a parent
work->moveToThread(sub); // 移动到子线程中工作
  • 启动子线程,调用 start(), 这时候线程启动了,但是移动到线程中的对象并没有工作。
  • 调用 MyWork 类对象的工作函数,让这个函数开始执行,这时候是在移动到的那个子线程中运行的。

示例代码

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

mywork.h

#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() 执行完毕, 子线程退出...";
}

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() 就可以了,这样可以让编写的程序更加灵活,可读性更强,更易于维护。


标签:include,Qt,void,MyWork,线程,使用,多线程,QThread,函数
From: https://blog.51cto.com/u_15461374/6037006

相关文章

  • 使用 Python 操作 Redis 数据库
    1.简介Redis是是一个高性能的key-value数据库。Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。Redis不仅仅支持简单的key-......
  • 使用 Python 操作 Mongo 数据库
    1.简介MongoDB是一个基于分布式文件存储的数据库,旨在为WEB应用提供可扩展的高性能数据存储解决方案。MongoDB是一个介于关系数据库和非关系数据库之间的产品,它支持的查......
  • PyQT调用ui界面文件
    通过QtDesigner将ui文件转存为py文件不继承Ui_FormimportsysfromPySide6importQtCore,QtWidgetsfromPySide6.QtWidgetsimport*fromui_testimportUi_Form......
  • MyBatis的使用六(解决字段名与成员名不一致)
    本文主要讲述mybatis如何解决mysql的字段名与java实体类的成员变量名称不一致。一.介绍实体类和数据表1.实体类EmployeepublicclassEmployee{privateIn......
  • 使用谱聚类(spectral clustering)进行特征选择
    在本文中,我们将介绍一种从相关特征的高维数据中选择或提取特征的有用方法。谱聚类是一种基于图论的聚类方法,通过对样本数据的拉普拉斯矩阵的特征向量进行聚类,从而达到对样......
  • 问题1-在使用 MQ 的时候,怎么确保消息 100% 不丢失?
    案例背景以京东系统为例,用户在购买商品时,通常会选择用京豆抵扣一部分的金额,在这个过程中,交易服务和京豆服务通过MQ消息队列进行通信。在下单时,交易服务发送“扣减账户X......
  • 博客园如何使用官方提供的皮肤
     第一步:进入官网,https://www.cnblogs.com/(按如下截图红框所示操作)  第二步:选择自己喜欢的皮肤应用就可以。  ......
  • 云原生之使用docker部署NTP时间服务器
    (云原生之使用docker部署NTP时间服务器)一、chrony介绍chrony是网络时间协议(NTP)的通用实现。它可以将系统时钟与NTP服务器、参考时钟(例如GPS接收器)以及使用手表......
  • window系统使用技巧
    一、如何以管理员的身份运行程序?方式1先找到应用程序,右击以管理员身份运行  方式2,找到程序所在位置--属性--兼容性--以管理员身份运行   ......
  • 第六十五章 使用 SNMP 监控 IRIS - 用户定义的 SNMP 监视器类示例
    第六十五章使用SNMP监控IRIS-用户定义的SNMP监视器类示例本节介绍如何定义可通过SNMP查询的用户应用程序监视器类的示例。应用程序监视器仅包括SNMP数据中具......