Qt—多线程基础

一、QThread

1.为什么使用多线程

在默认情况下,Qt使用的是单线程,当你启动一个 Qt 应用程序时,它会运行在一个单一的主线程(也被称为 GUI 线程)中。这个主线程负责处理所有的 GUI 事件和界面渲染。

但在一些其他情况下,单线程并不满足于我们的使用,所以多线程的使用就显得尤为重要。Qt 提供了丰富的多线程支持 ,允许你创建和管理额外的线程,从而在后台执行耗时操作,避免阻塞主线程。这样可以确保即使在执行复杂计算或 I/O 操作时,应用程序的界面仍然保持响应。

在使用多线程时,需要包含<QThread>

2.常用的API

公共成员函数

cpp 复制代码
// 构造函数
QThread::QThread(QObject *parent = Q_NULLPTR);

//判断线程中的任务是否处理完毕
bool isFinished() const;

//判断子线程中是否在执行任务
bool isRunning() const;

//设置线程的优先级
void setPriority(QThread::Priority priority)

//查看线程的优先级
QThread::Priority priority() const;

 退出线程, 停止底层的事件循环
// 退出线程的工作函数
void QThread::exit(int returnCode = 0);

// 调用线程退出函数之后, 线程不会马上退出因为当前任务有可能还没有完成, 调回用这个函数是
// 等待任务完成, 然后退出线程, 一般情况下会在 exit() 后边调用这个函数
bool QThread::wait(unsigned long time = ULONG_MAX);

信号

cpp 复制代码
// 任务函数中的处理逻辑执行完毕了
[signal] void QThread::finished();

// 开始工作之前发出这个信号
[signal] void QThread::started();

槽函数

cpp 复制代码
// 和调用 exit() 效果是一样的
[slot] void QThread::quit();

// 启动子线程
[slot] void QThread::start(Priority priority = InheritPriority);

// 线程退出, 可能是会马上终止线程
[slot] void QThread::terminate();

静态函数

cpp 复制代码
// 返回一个指向管理当前执行线程的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()函数

QThread最重要的函数,他是一个虚函数,run()函数中就是你需要用到的子线程任务,使用时需要写一个子类让其继承QThread,并且在子类中重写父类的run()方法。

**这个函数是一个受保护的成员函数,**不能够在类的外部调用,如果想要让线程执行这个函数中的业务流程,需要通过当前线程对象调用槽函数start()启动子线程,当子线程被启动,这个run()函数也就在线程内部被调用了。

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

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

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

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

二、多线程的两种使用方法

第一种:

  • 创建一个线程类的子类,让其继承QT中的线程类 QThread
  • 重写父类的 run() 方法,在该函数内部编写子线程要处理的具体的业务流程
  • 在线程中创建子线程对象
  • 调用start()方法启动子线程

第二种:

  • 创建一个继承QObject的类

  • 在这个类中添加一个公共的成员函数,函数体就是我们要子线程中执行的业务逻辑(函数可以是多个,也可以传参)

  • 在主线程中创建一个QThread对象, 这就是子线程的对象

  • 在主线程中创建工作的类对象(不要指定父对象)

  • 调用QObject类提供的moveToThread()方法将创建工作的类对象移动到线程对象中

  • 调用start()方法启动子线程

三、多线程的实现

本次实现中我们需要设置两子个子线程,第一个子线程计算1-10的累加;第二个子线程计算1-10的累乘。

1.第一种方法

首先创建UI界面

分别创建继承QT中的线程类 QThread的两个子类:add和multiply

add.h和add.cpp

multiply.h和multiply.cpp

分别在add和multiply中重写run()函数

add.h和add.cpp

multiply.h和multiply.cpp

在主线程按照步骤进行创建对象、启动子线程

最后运行结果,发现三个线程的地址均不一样。

最后别忘了析构

代码展示

add.h

cpp 复制代码
#ifndef ADD_H
#define ADD_H

#include <QThread>

class add : public QThread
{
    Q_OBJECT
public:
    explicit add(QObject *parent = nullptr);
private:
    void run() override;

signals:
    int finished(int);//线程结束后发送信号给主线程

};

#endif // ADD_H

add.cpp

cpp 复制代码
#include "add.h"
#include <QDebug>

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

}

void add::run()
{
    qDebug()<<"累加的线程地址为:"<<QThread::currentThread();//打印子线程地址
    int sum=0;
    for(int i=1;i<=10;++i)
    {
        sum+=i;
    }

    //线程结束后发射信号
    emit finished(sum);
}

multiply.h

cpp 复制代码
#ifndef MULTIPLY_H
#define MULTIPLY_H

#include <QThread>

class multiply : public QThread
{
    Q_OBJECT
public:
    explicit multiply(QObject *parent = nullptr);
private:
    void run() override;

signals:
    int finished(int);//线程结束后发送信号给主线程

};

#endif // MULTIPLY_H

multiply.cpp

cpp 复制代码
#include "multiply.h"
#include <QDebug>

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

}

void multiply::run()
{
    qDebug()<<"累乘的线程地址为:"<<QThread::currentThread();//打印子线程地址
    int sum=1;
    for(int i=1;i<=10;++i)
    {
        sum*=i;
    }

    //线程结束后发射信号
    emit finished(sum);
}

widdget,cpp

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include "add.h"
#include "multiply.h"
#include <QDebug>
#include <QThread>

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

    qDebug() << "主线程对象地址:  " << QThread::currentThread();
    //创建子线程对象
    add *Add=new add;
    multiply *Multiply=new multiply;

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

    //子线程启动结束后,会发生finied信号给主线程,主线程接收信号的值并显示在界面上
    connect(Add,&add::finished,this,[=](int num){
        QString str = QString::number(num);
        ui->add->setText("1-10的累加为:"+str);
    });
    connect(Multiply,&multiply::finished,this,[=](int num){
        QString str = QString::number(num);
        ui->multiply->setText("1-10的累乘为:"+str);
    });
    
    connect(this,&QThread::destroyed,this,[=](){
        Add->quit();
        Add->wait();
        Add->deleteLater();
        
        Multiply->quit();
        Multiply->wait();
        Multiply->deleteLater();
    });

}

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

2.第二种方法

首先创建UI界面

创建两个基于QObject类的函数,add和multiply

add.h和add.cpp

multiply.h和multiply.cpp

在这两个类中重写处理任务的函数(不同与run()函数,这两个类的函数可以传参,也可以是很多个)

add.h和add.cpp

multiply.h和multiply.cpp

在主线程按照步骤进行创建对象、子线程的创建,并把创建对象移动到子线程,最后启动子线程

运行结果发现三个线程的地址都不一样

最后别忘了析构

代码展示

add.h

cpp 复制代码
#ifndef ADD_H
#define ADD_H

#include <QObject>

class add : public QObject
{
    Q_OBJECT
public:
    explicit add(QObject *parent = nullptr);
    void working();


signals:
    int finished(int);//线程结束后发送信号给主线程

};

#endif // ADD_H

add.cpp

cpp 复制代码
#include "add.h"
#include <QDebug>
#include <QThread>

add::add(QObject *parent) : QObject(parent)
{

}

void add::working()
{
    qDebug() << "累加的线程地址:  " << QThread::currentThread();
    int sum=0;
    for(int i=1;i<=10;++i)
    {
        sum+=i;
    }
    emit finished(sum);
}

multiply.h

cpp 复制代码
#ifndef MULTIPLY_H
#define MULTIPLY_H

#include <QObject>

class multiply : public QObject
{
    Q_OBJECT
public:
    explicit multiply(QObject *parent = nullptr);
    void working();


signals:
    int finished(int);//线程结束后发送信号给主线程

};

#endif // MULTIPLY_H

multiply.cpp

cpp 复制代码
#include "multiply.h"
#include <QDebug>
#include <QThread>

multiply::multiply(QObject *parent) : QObject(parent)
{

}

void multiply::working()
{
    qDebug() << "累加的线程地址:  " << QThread::currentThread();
    int sum=1;
    for(int i=1;i<=10;++i)
    {
        sum*=i;
    }

    emit finished(sum);
}

widget.cpp

cpp 复制代码
#include "multiply.h"
#include <QDebug>
#include <QThread>

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

    qDebug() << "主线程对象地址:  " << QThread::currentThread();
    //创建对象
    add *Add=new add;
    multiply *Multiply=new multiply;

    //创建子线程对象
    QThread *t1=new QThread;
    QThread *t2=new QThread;

    //把对象移动到子线程中
    Add->moveToThread(t1);
    Multiply->moveToThread(t2);

    //启动两个子线程
    t1->start();
    t2->start();

    connect(ui->start,&QPushButton::clicked,Add,&add::working);
    connect(ui->start,&QPushButton::clicked,Multiply,&multiply::working);

    //子线程启动结束后,会发生finied信号给主线程,主线程接收信号的值并显示在界面上
    connect(Add,&add::finished,this,[=](int num){
        QString str = QString::number(num);
        ui->add->setText("1-10的累加为:"+str);
    });
    connect(Multiply,&multiply::finished,this,[=](int num){
        QString str = QString::number(num);
        ui->multiply->setText("1-10的累乘为:"+str);
    });

    connect(this,&QThread::destroyed,this,[=](){
        t1->quit();
        t1->wait();
        t1->deleteLater();

        t2->quit();
        t2->wait();
        t2->deleteLater();

        Add->deleteLater();
        Multiply->deleteLater();
    });

}

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

四、两种方法的比较

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

第二种方法 用起来更加灵活、可读性更强,更易于维护,但是这种方式写起来会相对复杂一些

综上所述,子线程的任务少和任务简单的情况下推荐使用第一种;子线程的任务多和任务复杂的情况下推荐使用第二种。

相关推荐
乌鸦9449 分钟前
《类和对象(下)》
开发语言·c++·类和对象+
炒空心菜菜20 分钟前
SparkSQL 连接 MySQL 并添加新数据:实战指南
大数据·开发语言·数据库·后端·mysql·spark
多多*44 分钟前
算法竞赛相关 Java 二分模版
java·开发语言·数据结构·数据库·sql·算法·oracle
前进的程序员1 小时前
嵌入式开发中 C++ 跨平台开发经验与解决方案
开发语言·c++
乌夷1 小时前
axios结合AbortController取消文件上传
开发语言·前端·javascript
神仙别闹1 小时前
基于C#+MySQL实现(WinForm)企业设备使用信息管理系统
开发语言·mysql·c#
czhaii2 小时前
PLC脉冲位置 单片机跟踪读取记录显示
开发语言·c#
alden_ygq2 小时前
当java进程内存使用超过jvm设置大小会发生什么?
java·开发语言·jvm
蜗牛沐雨2 小时前
Rust 中的 `PartialEq` 和 `Eq`:深入解析与应用
开发语言·后端·rust
Python私教2 小时前
Rust快速入门:从零到实战指南
开发语言·后端·rust