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()函数中,这样该函数中的处理逻辑就会变得非常混乱,不太容易维护。

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

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

相关推荐
用户805533698033 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner3 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz8 天前
QML Hello World 入门示例
qt
xcyxiner11 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner11 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner12 天前
DicomViewer (添加模型类)3
qt
xcyxiner12 天前
DicomViewer (目录调整) 2
qt
xcyxiner12 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00614 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术14 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript