Qt中关于多线程QThread的使用笔记

使用环境:Ubuntu16.04+Qtcreator-5.0.0

强烈推荐贼棒的Qt学习资料:https://subingwen.cn/qt/

Qt 中多线程的使用:https://subingwen.cn/qt/thread/

视频教程:https://www.bilibili.com/video/BV1iN411f7dY?p=1

关于Qt中的emit()函数

在这里插入图片描述

关于Qt中的元对象系统

  在mythread.h文件中声明信号函数mess()和Display(const cv::Mat *image, int index),运行之后,会自动生成moc_mythread.cpp文件

https://blog.csdn.net/m0_45867846/article/details/107585903
https://blog.csdn.net/weixin_52511809/article/details/115549335

1.Qt中使用多线程的必要性

  使用多线程会减少界面的卡顿,让程序的运行更加流畅,提升用户的使用体验。举例:正常启动一个Qt程序,默认只有一个线程,我们在拖动UI界面的时候,窗口也会随着鼠标拖动并且显示其内容,倘若现在线程正在执行一个复杂的逻辑程序,这时再拖动窗口时,窗口并不会随着鼠标的拖动而移动,会显示无响应的状态。此时就需要使用多线程来处理,让一个子线程来处理复杂的逻辑程序,让主线程来实时显示窗口的内容,两个线程之间各不影响,子线程处理完毕后会把结果发送给主线程,最后再通过主线程将结果显示在窗口中。

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

1.1 使用时的注意点

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

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

2.线程类 QThread

  通过Qt帮助文档,查询QThread类相关信息,包括公共函数、公共槽函数、信号、静态成员函数等。
在这里插入图片描述

2.1 常用共用成员函数

  • QThread 类常用 API
  • 构造函数——指定一个父对象即可(父类和父对象的区别:父对象是按照从属关系来的,在创建一个对象的时候,Qt是通过对象树来完成内存回收的。假设在对象A下面有一个对象B,则对象B是对象A的子对象,在析构对象A的时候,程序首先会析构对象B。对象树中成员之间的关系并不是都是继承关系(父类中是按照继承关系来的)。)
QThread::QThread(QObject *parent = Q_NULLPTR);
  • 判断线程的状态
// 判断线程中的任务是不是处理完毕了,初始false,start()之后进入run(),只要是run()没有退出就是true
bool QThread::isFinished() const;

// 判断子线程是不是在执行任务,正在执行返回true,初始为false,在run()执行完一次之后为true,再次start()后重置为false
bool QThread::isRunning() const;

// 在耗时操作中使用isInterruptionRequested()来判断是否请求终止线程,如果没有,则一直运行;当希望终止线程的时候,调用requestInterruption()即可。
// 这个是Qt内部通过锁实现的(安全的),通过requestInterruption()请求中断和wait()使得线程退出(前提是run()中用了isInterruptionRequested()判断)
// 这个返回值只有再requestInterruption()后,且wait()之间为true,其他时间都是false
bool QThread::isInterruptionRequested() const;
  • Qt中线程的优先级,通过枚举类型来描述。如果把某个子线程的优先级调低了,那么该线程抢到CPU时间片的概率就降低了。
// 得到当前线程的优先级
Priority QThread::priority() const;

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

// 优先级:
    QThread::IdlePriority          0  --> 最低的优先级
    QThread::LowestPriority        1
    QThread::LowPriority           2
    QThread::NormalPriority        3
    QThread::HighPriority          5
    QThread::HighestPriority       6
    QThread::TimeCriticalPriority  7 --> 最高的优先级
    QThread::InheritPriority       8 --> 表示创建的子线程的优先级跟随创建它的主线程,子线程和其父线程的优先级相同, 默认是这个
  • 退出当前线程和等待当前线程的结束——搭配使用
// 退出线程, 停止底层的事件循环
// 退出线程的工作函数
void QThread::exit(int returnCode = 0);

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

2.2 信号和槽函数

  • 信号
// 和调用 exit() 效果是一样的
// 调用这个函数之后, 再调用 wait() 函数
[slot] void QThread::quit();

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

// 线程退出, 可能是会马上终止线程,不再需要调用wait()函数了, 一般情况下不使用这个函数
[slot] void QThread::terminate();
  • 槽函数
// 线程中执行的任务完成了, 发出该信号
// 任务函数中的处理逻辑执行完毕了
[signal] void QThread::finished();

// 开始工作之前发出这个信号, 一般不使用
[signal] void QThread::started();

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

2.4 任务处理函数

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

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

3.Qt中线程的使用方式一

3.1 操作步骤

  1. 需要创建一个线程类的子类,让其继承 QT 中的线程类 QThread,比如在 mythread.h 中写下:
class MyThread:public QThread
{
    ......
    public:
    MyThread();
    ~MyThread();

    void run();

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

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

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

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

3.2 举例一

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

  • 不使用多线程
    在这里插入图片描述
    在这里插入图片描述
    mainwindow.cpp
#include "mainwindow.h"
#include "./ui_mainwindow.h"

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

    connect(ui->pbn_start, &QPushButton::clicked, this, [=]() {
        int num = 0;
        while (1) {
            num++;
            if (num == 10000000) {
                break;
            }
            ui->lbl_show->setNum(num);
        }
    });
}

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

效果:
  点击开始按钮,会发现数字并不会在窗口中时时更新,并且这时候如果用户使用鼠标拖动窗口,就会出现无响应的情况,在过了一会儿后界面上显示9999999。
在这里插入图片描述

  • 使用多线程
      思路:在界面窗口中,点击按钮开始在子线程中数数,让后通过信号槽机制将数据传递给 UI 线程,通过 UI 线程将数据更新到窗口中。
    在这里插入图片描述
    在这里插入图片描述

mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>

class MyThread : public QThread
{
    Q_OBJECT
public:
    MyThread();
    ~MyThread();

protected:
    void run();

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

#endif // MYTHREAD_H

mythread.cpp

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

MyThread::MyThread()
{
}

MyThread::~MyThread()
{
}

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

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

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

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->lbl_show->setNum(num); });

    connect(ui->pbn_start, &QPushButton::clicked, this, [=]() {
        // 启动子线程
        subThread->start();
    });
	
	// 线程资源释放
	connect(this, &MainWindow::destroyed, this, [=]() {
        subThread->quit();
        subThread->wait();
        subThread->deleteLater();
    });
}

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

效果:
  点击开始按钮,会发现数字在窗口中时时更新,并且这用户使用鼠标拖动窗口时界面上面的数字也在更新。
在这里插入图片描述

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

3.2 举例二

  需求:主线程A中生成10000个随机数并进行显示, 子线程B中对主线程A中生成的随机数进行冒泡排序,子线程C中对主线程A中生成的随机数进行快速排序,两种排序方法不一样,并对两种方法的排序时间进行统计和分别显示。

  思路:生成的随机数个数由主线程发送给子线程,子线程生成了随机数之后,将这些数据通过信号槽发送给主线程进行显示,(子线程不能直接把随机数初始化到ui界面上,子线程没有资格直接对窗口直接进行读写操作)
在这里插入图片描述
在这里插入图片描述
mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>
#include <QVector>

// 生成随机数的子线程类
class Generate : public QThread
{
    Q_OBJECT
public:
    Generate();
    ~Generate();

    // 从主线程中获取子线程中应该生成的随机数个数
    void receNum(int num);

protected:
    void run() override;

signals:
    // 将子线程中生成的随机数通过信号发送给主线程进行显示
    void sendArray(QVector<int> num);

private:
    int m_num_;
};

// 对随机进行冒泡排序的子线程类
class BubbleSort : public QThread
{
    Q_OBJECT
public:
    BubbleSort();
    ~BubbleSort();

    // 从主线程中获取需要进行冒泡排序的随机数
    void receArray(QVector<int> list);

protected:
    void run() override;

signals:
    // 子线程进行冒泡排序完成后,将排序好了的数发送给主线程进行显示
    void finish(QVector<int> num);

private:
    QVector<int> m_list_;
};

// 对随机进行快速排序的子线程类
class QuickSort : public QThread
{
    Q_OBJECT
public:
    QuickSort();
    ~QuickSort();

    // 从主线程中获取需要进行快速排序的随机数数
    void receArray(QVector<int> list);

protected:
    void run() override;

signals:
    // 子线程进行快速排序完成后,将排序好了的数发送给主线程进行显示
    void finish(QVector<int> num);

private:
    void quickSort(QVector<int> &list, int l, int r);

private:
    QVector<int> m_list_;
};

#endif // MYTHREAD_H

mythread.cpp

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

Generate::Generate()
{
}
Generate::~Generate()
{
}

void Generate::receNum(int num)
{
    m_num_ = num;
}

void Generate::run()
{
    qDebug() << "生成随机数的线程的线程地址:" << QThread::currentThread();

    QVector<int> list;
    QElapsedTimer time;
    time.start();
    for (int i = 0; i < m_num_; i++) {
        list.push_back(qrand() % 100000);
    }
    int milsec = time.elapsed();
    qDebug() << "生成" << m_num_ << "个随机数总共用时:" << milsec << "毫秒";

    // 将生成好的随机数通过信号发送给主线程
    emit sendArray(list);
}

BubbleSort::BubbleSort()
{
}
BubbleSort::~BubbleSort()
{
}

void BubbleSort::receArray(QVector<int> list)
{
    m_list_ = list;
}

void BubbleSort::run()
{
    qDebug() << "冒泡排序的线程的线程地址:" << QThread::currentThread();

    QElapsedTimer time;
    time.start();

    // 冒泡排序
    int temp;
    for (int i = 0; i < m_list_.size(); ++i) {
        for (int j = 0; j < m_list_.size() - i - 1; ++j) {
            if (m_list_[j] > m_list_[j + 1]) {
                temp = m_list_[j];
                m_list_[j] = m_list_[j + 1];
                m_list_[j + 1] = temp;
            }
        }
    }

    int milsec = time.elapsed();
    qDebug() << "冒泡排序用时:" << milsec << "毫秒";

    // 将冒泡排序好的随机数通过信号发送给主线程
    emit finish(m_list_);
}

QuickSort::QuickSort()
{
}

QuickSort::~QuickSort()
{
}

void QuickSort::receArray(QVector<int> list)
{
    m_list_ = list;
}

void QuickSort::run()
{
    qDebug() << "快速排序的线程的线程地址:" << QThread::currentThread();

    QElapsedTimer time;
    time.start();

    // 快速排序
    quickSort(m_list_, 0, m_list_.size() - 1);

    int milsec = time.elapsed();
    qDebug() << "快速排序用时:" << milsec << "毫秒";

    // 将快速排序好的随机数通过信号发送给主线程
    emit finish(m_list_);
}

void QuickSort::quickSort(QVector<int> &list, int l, int r)
{
    if (l < r) {
        int i = l, j = r;
        int x = list[l];
        while (i < j) {
            while ((i < j) && (list[j] >= x)) {
                j--;
            }
            if (i < j) {
                list[i++] = list[j];
            }
            while (i < j && list[i] < x) {
                i++;
            }
            if (i < j) {
                list[j--] = list[i];
            }
        }
        list[i] = x;
        quickSort(list, l, i - 1);
        quickSort(list, i + 1, r);
    }
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

signals:
    void starting(int num);

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

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

    // 1.创建子线程对象
    Generate *gen = new Generate;
    BubbleSort *bubble = new BubbleSort;
    QuickSort *quick = new QuickSort;

    // 信号发送者是当前窗口this对象,操作是发出信号starting,信号接收者是子线程对象gen,操作是调用槽函数recvNum。
    // 执行当前的connet操作后,只要是emit starting(10000)这个信号发出去后,
    // 子线程的recvNum就能接收到starting(10000)发送出去的实参10000。
    connect(this, &MainWindow::starting, gen, &Generate::receNum);

    // 2.启动子线程
    connect(ui->pbn_start, &QPushButton::clicked, this, [=]() {
        // 主线程发送信号
        emit starting(10000);
        gen->start();
    });

    // 将生成的随机数由gen对象传给两个子线程
    connect(gen, &Generate::sendArray, bubble, &BubbleSort::receArray);
    connect(gen, &Generate::sendArray, quick, &QuickSort::receArray);
    // 3.接收子线程发送的数据
    connect(gen, &Generate::sendArray, this, [=](QVector<int> list) {
        bubble->start();
        quick->start();
        for (int i = 0; i < list.size(); i++) {
            ui->lsw_random->addItem(QString::number(list.at(i)));
        }
    });
    // 将两个排序子线程排序好了的随机数传送给主线程进行显示
    connect(bubble, &BubbleSort::finish, this, [=](QVector<int> list) {
        for (int i = 0; i < list.size(); i++) {
            ui->lsw_bubble_sort->addItem(QString::number(list.at(i)));
        }
    });
    connect(quick, &QuickSort::finish, this, [=](QVector<int> list) {
        for (int i = 0; i < list.size(); i++) {
            ui->lsw_quick_sort->addItem(QString::number(list.at(i)));
        }
    });

	// 线程资源释放
	connect(this, &MainWindow::destroyed, this, [=]() {
        gen->quit();
        gen->wait();
        gen->deleteLater();

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

        quick->quit();
        quick->wait();
        quick->deleteLater();
    });
}

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

效果:
在这里插入图片描述

4.Qt中线程的使用方式二

  Qt 提供的第二种线程的创建方式弥补了第一种方式的缺点,用起来更加灵活,但是这种方式写起来会相对复杂一些。

4.1 操作步骤

  1. 创建一个新的类,让这个类从 QObject 派生
class MyWork:public QObject
{
    .......
}
  1. 在这个类中添加一个公共的成员函数,函数体就是我们要子线程中执行的业务逻辑,可以有多个working()
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 类对象的工作函数,让这个函数开始执行,这时候是在移动到的那个子线程中运行的

4.2 举例一

  假如只有一个线程,让其一直数数,会发现数字并不会在窗口中时时更新,并且这时候如果用户使用鼠标拖动窗口,就会出现无响应的情况,使用多线程就不会出现这样的现象了。
在这里插入图片描述
mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>

class MyThread : public QThread
{
    Q_OBJECT
public:
    MyThread();
    ~MyThread();

public:
    // 工作函数
    void working();

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

#endif // MYTHREAD_H

mythread.cpp

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

MyThread::MyThread()
{
}

MyThread::~MyThread()
{
}

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

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

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

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

    // 创建线程对象
    QThread *sub = new QThread;

    // 创建工作的类对象
    // 千万不要指定给创建的对象指定父对象
    // 如果指定了: QObject::moveToThread: Cannot move objects with a parent
    MyThread *work = new MyThread;

    // 将工作的类对象移动到创建的子线程对象中
    work->moveToThread(sub);
    // 启动线程
    sub->start();

    // 让工作的对象开始工作, 点击开始按钮, 开始工作
    connect(ui->pbn_start, &QPushButton::clicked, work, &MyThread::working);

    // 显示数据
    connect(work, &MyThread::curNumber, this,
            [=](int num) { ui->lbl_show->setNum(num); });

	// 线程资源释放
    connect(this, &MainWindow::destroyed, this, [=]() {
        sub->quit();
        sub->wait();
        sub->deleteLater();
    });
}

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

效果:
和方式一的运行效果一致。

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

4.2 举例二

  需求:主线程A中生成10000个随机数并进行显示, 子线程B中对主线程A中生成的随机数进行冒泡排序,子线程C中对主线程A中生成的随机数进行快速排序,两种排序方法不一样,并对两种方法的排序时间进行统计和分别显示。
在这里插入图片描述
mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QObject>
#include <QVector>

// 生成随机数的子线程类
class Generate : public QObject
{
    Q_OBJECT
public:
    Generate();
    ~Generate();

    void working(int num);

signals:
    // 将子线程中生成的随机数通过信号发送给主线程进行显示
    void sendArray(QVector<int> num);
};

// 对随机进行冒泡排序的子线程类
class BubbleSort : public QObject
{
    Q_OBJECT
public:
    BubbleSort();
    ~BubbleSort();

    void working(QVector<int> list);

signals:
    // 子线程进行冒泡排序完成后,将排序好了的数发送给主线程进行显示
    void finish(QVector<int> num);
};

// 对随机进行快速排序的子线程类
class QuickSort : public QObject
{
    Q_OBJECT
public:
    QuickSort();
    ~QuickSort();

    void working(QVector<int> list);

signals:
    // 子线程进行快速排序完成后,将排序好了的数发送给主线程进行显示
    void finish(QVector<int> num);

private:
    void quickSort(QVector<int> &list, int l, int r);
};

#endif // MYTHREAD_H

mythread.cpp

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

Generate::Generate()
{
}
Generate::~Generate()
{
}

void Generate::working(int num)
{
    qDebug() << "生成随机数的线程的线程地址:" << QThread::currentThread();

    QVector<int> list;
    QElapsedTimer time;
    time.start();
    for (int i = 0; i < num; i++) {
        list.push_back(qrand() % 100000);
    }
    int milsec = time.elapsed();
    qDebug() << "生成" << num << "个随机数总共用时:" << milsec << "毫秒";

    // 将生成好的随机数通过信号发送给主线程
    emit sendArray(list);
}

BubbleSort::BubbleSort()
{
}
BubbleSort::~BubbleSort()
{
}

void BubbleSort::working(QVector<int> list)
{
    qDebug() << "冒泡排序的线程的线程地址:" << QThread::currentThread();

    QElapsedTimer time;
    time.start();

    // 冒泡排序
    int temp;
    for (int i = 0; i < list.size(); ++i) {
        for (int j = 0; j < list.size() - i - 1; ++j) {
            if (list[j] > list[j + 1]) {
                temp = list[j];
                list[j] = list[j + 1];
                list[j + 1] = temp;
            }
        }
    }

    int milsec = time.elapsed();
    qDebug() << "冒泡排序用时:" << milsec << "毫秒";

    // 将冒泡排序好的随机数通过信号发送给主线程
    emit finish(list);
}

QuickSort::QuickSort()
{
}

QuickSort::~QuickSort()
{
}

void QuickSort::working(QVector<int> list)
{
    qDebug() << "快速排序的线程的线程地址:" << QThread::currentThread();

    QElapsedTimer time;
    time.start();

    // 快速排序
    quickSort(list, 0, list.size() - 1);

    int milsec = time.elapsed();
    qDebug() << "快速排序用时:" << milsec << "毫秒";

    // 将快速排序好的随机数通过信号发送给主线程
    emit finish(list);
}

void QuickSort::quickSort(QVector<int> &list, int l, int r)
{
    if (l < r) {
        int i = l, j = r;
        int x = list[l];
        while (i < j) {
            while ((i < j) && (list[j] >= x)) {
                j--;
            }
            if (i < j) {
                list[i++] = list[j];
            }
            while (i < j && list[i] < x) {
                i++;
            }
            if (i < j) {
                list[j--] = list[i];
            }
        }
        list[i] = x;
        quickSort(list, l, i - 1);
        quickSort(list, i + 1, r);
    }
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

signals:
    void starting(int num);

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

mainwindow.cpp

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

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

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

    // 2.创建任务类的对象
    Generate *gen = new Generate;
    BubbleSort *bubble = new BubbleSort;
    QuickSort *quick = new QuickSort;

    // 3.将任务对象移动到某个子线程中
    gen->moveToThread(t1);
    bubble->moveToThread(t2);
    quick->moveToThread(t3);

    // 信号发送者是当前窗口this对象,操作是发出信号starting,信号接收者是子线程对象gen,操作是调用函数working。
    connect(this, &MainWindow::starting, gen, &Generate::working);

    // 启动子线程
    connect(ui->pbn_start, &QPushButton::clicked, this, [=]() {
        // 主线程发送信号
        emit starting(10000);
        t1->start();
    });

    // 将生成的随机数由gen对象传给两个子线程
    connect(gen, &Generate::sendArray, bubble, &BubbleSort::working);
    connect(gen, &Generate::sendArray, quick, &QuickSort::working);
    // 接收子线程发送的数据
    connect(gen, &Generate::sendArray, this, [=](QVector<int> list) {
        t2->start();
        t3->start();
        for (int i = 0; i < list.size(); i++) {
            ui->lsw_random->addItem(QString::number(list.at(i)));
        }
    });
    // 将两个排序子线程排序好了的随机数传送给主线程进行显示
    connect(bubble, &BubbleSort::finish, this, [=](QVector<int> list) {
        for (int i = 0; i < list.size(); i++) {
            ui->lsw_bubble_sort->addItem(QString::number(list.at(i)));
        }
    });
    connect(quick, &QuickSort::finish, this, [=](QVector<int> list) {
        for (int i = 0; i < list.size(); i++) {
            ui->lsw_quick_sort->addItem(QString::number(list.at(i)));
        }
    });

    // 线程资源释放
    connect(this, &MainWindow::destroyed, this, [=]() {
        t1->quit();
        t1->wait();
        t1->deleteLater();

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

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

        gen->deleteLater();
        bubble->deleteLater();
        quick->deleteLater();
    });
}

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

效果:
在这里插入图片描述

Qt 中线程池的使用:
https://subingwen.cn/qt/threadpool/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

boss-dog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值