Qt04多线程

一、知识回顾

1,为什么需要多线程?

  • 从计算机底层来说: 线程可以比作是轻量级的进程,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。另外,多核 CPU 时代意味着多个线程可以同时运行,这减少了线程上下文切换的开销。
  • 从当代互联网发展趋势来说 现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。

再深入到计算机底层来探讨:

  • 单核时代:在单核时代多线程主要是为了提高 CPU 和 IO 设备的综合利用率。举个例子:当只有一个线程的时候会导致 CPU 计算时,IO 设备空闲;进行 IO 操作时,CPU 空闲。我们可以简单地说这两者的利用率目前都是 50%左右。但是当有两个线程的时候就不一样了,当一个线程执行 CPU 计算时,另外一个线程可以进行 IO 操作,这样两个的利用率就可以在理想情况下达到 100%了。
  • 多核时代: 多核时代多线程主要是为了提高 CPU 利用率。举个例子:假如我们要计算一个复杂的任务,我们只用一个线程的话,CPU 只会一个 CPU 核心被利用到,而创建多个线程就可以让多个 CPU 核心被利用到,这样就提高了 CPU 的利用率。

2,进程和线程的区别:

进程:一个独立的程序,拥有独立的虚拟地址空间,要和其他进程通信,需要使用进程通信的机制。

线程:没有自己的资源,都是共享进程的虚拟地址空间,多个线程通信存在隐患。

PS:在操作系统每一个进程都拥有独立的内存空间,线程的开销远小于进程,一个进程可以拥有多个线程。(因此我们常用多线程并发,而非多进程并发)

为了更容易理解多线程的作用,先看一个实例:

在主线程中运行一个10s耗时的操作。(通过按钮来触发)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "threadtest.h"
#include"qthread.h"
Threadtest::Threadtest(QWidget* parent)
: QMainWindow(parent)
{
ui.setupUi(this);
connect(ui.btn_start, &QPushButton::clicked, this,
&Threadtest::on_pushButton_clicked);

}
void Threadtest::on_pushButton_clicked()
{
QThread::sleep(10);//主线程
}

可以看到程序运行过程中,整个线程都在响应10秒的耗时操作,对于线程的消息循环exec()函数就未响应了(就是你在这个过程中拖动界面是无反应的)

二、线程类QThread

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

  • 2.1 常用共用成员函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 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);
  • 2.2 信号槽
1
2
3
4
5
6
7
8
9
10
11
12
13
// 和调用 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();
  • 2.3静态函数
1
2
3
4
5
6
7
8
// 返回一个指向管理当前执行线程的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); // 单位: 微秒

三、Qt中实现多线程的两种方法

🧡🧡3.1.派生QThread类对象的方法(重写Run函数)

首先,以文字形式来说明需要哪几个步骤。

  1. 自定义一个自己的类,使其继承自QThread类;
  2. 在自定义类中覆写QThread类中的虚函数run()。

这很可能就是C++中多态的使用。补充一点:QThread类继承自QObject类。

这里要重点说一下run()函数了。它作为线程的入口,也就是线程从run()开始执行,我们打算在线程中完成的工作都要写在run()函数中,个人认为可以把run()函数理解为线程函数。这也就是子类覆写基类的虚函数,基类QThread的run()函数只是简单启动exec()消息循环,关于这个exec()后面有很多东西要讲,请做好准备。
那么我们就来尝试用多线程实现10s耗时的操作:(用按钮触发)

  • 1️⃣在编辑好ui界面后,先创建一个workThread的类。(继承自QThread类)

  • 2️⃣在workThread1的类中重写run函数

在workThread.h的声明run函数:

1
2
3
4
5
6
#include <qthread.h>
class workThread : public QThread
{
public:
void run();
};

在workThread.cpp中重写run函数(并打印子线程的ID):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "workThread.h"
#include"qdebug.h"
workThread::workThread(QObject* parent)
{

}
//重写run函数
void workThread::run()
{
qDebug() << "当前子线程ID:" << QThread::currentThreadId();
qDebug() << "开始执行线程";
QThread::sleep(10);
qDebug() << "线程结束";

}
  • 3️⃣在主类中启动线程

threadtest.h中声明线程和按钮事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <QtWidgets/QMainWindow>
#include "ui_threadtest.h"
#include"workThread.h"
#pragma execution_character_set("utf-8")
class Threadtest : public QMainWindow
{
Q_OBJECT

public:
Threadtest(QWidget *parent = Q_NULLPTR);


private:
Ui::ThreadtestClass ui;
void btn_clicked();
workThread* thread;
};

threadtest.cpp中实现,并启动子线程线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "threadtest.h"
#include "qthread.h"
#include "qdebug.h"

Threadtest::Threadtest(QWidget* parent)
: QMainWindow(parent)
{
ui.setupUi(this);
connect(ui.btn_start, &QPushButton::clicked, this, &Threadtest::btn_clicked);
thread = new workThread(this);
}
void Threadtest::btn_clicked()
{
qDebug() << "主线程id:" << QThread::currentThreadId();
thread->start();//启动子线程
}

可以实现,在执行耗时操作时也可拖动界面。

需要注意的是:

在主程序中添加workThread类的头文件时,需要将workThread.h放在threadtest.h中(不然会报错!!!!)

使用QThread::currentThreadId()来查看当前线程的ID,无论是子线程还是主线程,不同线程其ID是不同的。注意,这是一个静态函数,因此可以不经过对象来调用。

创建的workThread1类的执行实际上是在主线程里的,只有run函数内的程序才会在子线程中执行!(即QThread只是线程的管理类,只有run()才是我们的线程函数)

因此在QThread(即创建的类)中的成员变量属于主线程,在访问前需要判断访问是否安全。run()中创建的变量属于子线程。

线程之间共享内存是不安全的(由于多线程争夺资源会影响数据安全问题),解决的办法就是要上锁。

关于互斥锁

个人认为,exec()这个点太重要了,同时还不太容易理解。

比如下面的代码中有两个exec(),我们讲“一山不容二虎”,放在这里就是说,一个线程中不能同时运行两个exec(),否则就会造成另一个消息循环得不到消息。像QDialog模态窗口中的exec()就是因为在主线程中同时开了两个exec(),导致主窗口的exec()接收不到用户的消息了。但是!但是!但是!我们这里却没有任何问题,因为它们没有出现在同一个线程中,一个是主线程中的exec(),一个是子线程中的exec()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <QApplication>
#include <QThread>
#include <QDebug>

class MyThread:public QThread
{
public:
void run()
{
qDebug()<<"child thread begin"<<endl;
qDebug()<<"child thread"<<QThread::currentThreadId()<<endl;
QThread::sleep(5);
qDebugu()<<"QThread end"<<endl;
this->exec();
}
};

int main(int argc,char ** argv) //mian()作为主线程
{
QApplication app(argc,argv);

MyThread thread; //创建一个QThread派生类对象就是创建了一个子线程
thread.start(); //启动子线程,然后会自动调用线程函数run()

qDebug()<<"main thread"<<QThread::currentThreadId()<<endl;
QThread::sleep(5);
qDebugu()<<"main thread"<<QThread::currentThreadId()<<endl;

thread.quit(); //使用quit()或者exit()使得子线程能够退出消息循环,而不至于陷在子线程中
thread.wait(); //等待子线程退出,然后回收资源
//thread.wait(5000); //设定等待的时间

return app.exec();
}

如果run()函数中没有执行exec()消息循环函数,那么run()执行完了也就意味着子线程退出了。一般在子线程退出的时候需要主线程去回收资源,可以调用QThread的wait(),使主线程等待子线程退出,然后回收资源。这里wait()是一个阻塞函数,有点像C++11中的join()函数。

但是!但是!但是!run()函数中调用了exec()函数,exec()是一个消息循环,也可以叫做事件循环,也是会阻塞的,相当于一个死循环使子线程卡在这里永不退出,必须调用QThread的quit()函数或者exit()函数才可以使子线程退出消息循环,并且有时还不是马上就退出,需要等到CPU的控制权交给线程的exec()。

所以先要thread.quit();使退出子线程的消息循环, 然后thread.wait();在主线程中回收子线程的资源。

值得注意的有两点:子线程的exet()消息循环必须在run()函数中调用;如果没有消息循环的话,则没有必要调用quit( )或者exit(),因为调用了也不会起作用。

第一种创建线程的方式需要在run()中显式调用exec(),但是exec()有什么作用呢,目前还看不出来,需要在第二种创建线程的方式中才能知道。


💜💜3.2.使用信号与槽方式来实现多线程

刚讲完使用QThread派生类对象的方法创建线程,现在就要来说它一点坏话。这种方法存在一个局限性,只有一个run()函数能够在线程中去运行,但是当有多个函数在同一个线程中运行时,就没办法了,至少实现起来很麻烦。所以,当当当当,下面将介绍第二种创建线程的方式:使用信号与槽的方式,也就是把在线程中执行的函数(我们可以称之为线程函数)定义为一个槽函数。

仍然是首先以文字形式说明这种方法的几个步骤。

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

代码实例:

  • 1️⃣创建一个workThread的类。(继承自QThread类)

定义槽函数(子线程执行的程序都可以放在槽函数中

1
2
3
4
5
6
7
//workThread.cpp(先在workThread.h中声明槽函数)void workThread:: doWork()
{
qDebug()<<"当前线程ID:"<<QThread::currentThreadId();
qDebug()<<"开始执行";
QThread::sleep(10);
qDebug()<<"结束执行";
}
  • 2️⃣主线程中分别对workThread类和QTread类实例化

在threadtest.h中声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <QtWidgets/QMainWindow>
#include "ui_threadtest.h"
#include"workThread.h"
#include"qthread.h"
#pragma execution_character_set("utf-8")
class Threadtest : public QMainWindow
{
Q_OBJECT

public:
Threadtest(QWidget *parent = Q_NULLPTR);
private:
Ui::ThreadtestClass ui;
void btn_clicked();
workThread* thread; //实例化workThread类
QThread* qthread; //实例化QThread类
};

在threadtest.cpp中实现并通过moveToThread将自己放到线程QThread对象中,最后启动线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "threadtest.h"
#include"qthread.h"
#include"qdebug.h"

Threadtest::Threadtest(QWidget* parent)
: QMainWindow(parent)
{
ui.setupUi(this);

//不能指定自定义类的对象的父类为widget,即没有this(很重要!!!!)
thread = new workThread();
qthread1 = new QThread(this);
thread->moveToThread(qthread1);
//线程结束时清理线程内存
connect(qthread1, &QThread::finished, qthread1, &QThread::deleteLater);
//将按钮事件(信号)绑定槽函数
connect(ui.btn_start, &QPushButton::clicked, thread, &workThread::dowork);
//打印主线程
connect(ui.btn_start, &QPushButton::clicked, [=]() {
qDebug() << "主线程id:" << QThread::currentThreadId();
});
//线程启动
qthread1->start();
}

在运行槽函数时,不能在此直接调用(如:thread1->doWork())。而是用connect连接信号和槽

这里将打印主线程和槽函数都绑定在了button按钮的click事件上了。

  • 子线程中不能操作UI

Qt创建的子线程中是不能对UI对象进行任何操作的,即QWidget及其派生类对象,这个是我掉的第一个坑。可能是由于考虑到安全性的问题,所以Qt中子线程不能执行任何关于界面的处理,包括消息框的弹出。正确的操作应该是通过信号槽,将一些参数传递给主线程,让主线程(也就是Controller)去处理。

  • 自定义的类不能指定父对象

比如上面程序中的:(不能指定自定义类对象为widget,即不可以加this)

1
thread1=new workThread1();//初始化
  • QThread和connect的关系

我们使用最多的是QThread于connect的关系,在使用connect函数的时候,我们一般会把最后一个参数忽略掉。这时候我们需要看下函数原型:

1
[static] QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)

可以看到,最后一个参数代表的是连接的方式。我们一般会用到方式是有三种:

自动连接(AutoConnection):默认的连接方式。如果信号与槽,也就是发送者与接受者在同一线程,等同于直接连接;如果发送者与接受者处在不同线程,等同于队列连接。

直接连接(Direct Connection):当信号发射时,槽函数将直接被调用。无论槽函数所属对象在哪个线程,槽函数都在发射者所在线程执行。

队列连接(Queued Connection):当控制权回到接受者所在线程的事件循环式,槽函数被调用。槽函数在接收者所在线程执行。

因此我们需要注意的是:

  1. 主线程对象发出信号连接QThread子类的槽函数,QThread子类对象在主线程创建的,无论采用哪种连接方式,槽函数都属在主线程调用。(如果在重写的run函数中调用了槽函数,此时槽函数在次线程执行,注意数据安全)。
  2. 次线程run中发出信号,槽函数可以是发出信号对象自身的槽函数,自发自收,都是次线程中行。槽函数是QThread子类的槽函数,或者主线程中对象的槽函数,这里的种情况需要你指明run中connect中的连接方式,直连则该槽函数在次线程中执行(可能发生数据安全问题),列队则在主线程执行。

🧡🧡总结一下:

  • 一定要用信号槽机制,别想着直接调用,你会发现并没有在子线程中执行。
  • 自定义的类不能指定父对象,因为moveToThread函数会将线程对象指定为自定义的类的父对象,当自定义的类对象已经有了父对象,就会报错。
  • 当一个变量需要在多个线程间进行访问时,最好加上voliate关键字,以免读取到的是旧的值。当然,Qt中提供了线程同步的支持,比如互斥锁之类的玩意,使用这些方式来访问变量会更加安全。
  • 分析发出信号的对象和接受信号对象所在的线程,再通过连接方式,判断槽函数在哪里执行。(小白在使用中就有在run中创建对象-因为多非槽函数都需要在次线程中执行,通过指针引出来,再connect与其他模块交互,指明连接方式为列队形式,所以相关执行都在次线程中执行)。这里记住moveToThread只能将槽函数移到次线程中。

启动多线程的操作思路

如果我们需要实现一个排序操作,即首先获取1000个随机数,然后用冒泡排序法对其进行排序。

方法一:重写run函数

思路:(构建两个子线程,一个用于生成随机数,一个用于冒泡排序,主线程负责调用)

  • 新建myThread类,在该类中重写run函数(即生随机数)。需要先接收主线程发送的要生成随机数的个数(scvnum信号)后再进行生成,生成完成以后发送一个sendArray信号
  • 新建BubbleSort类,在该类中重写run函数(即冒泡排序算法)。需要先接收已经生成的随机数(rcvArray信号)后再进行排序,排序完成后发送一个finish信号
  • 主线程通过stating信号告诉myThread线程要生成的个数,然后myThread线程通过scvnum信号接收,进行生成随机数,然后再发送一个sendArray信号(即生成随机数集合)
  • BubbleSort子线程通过rcvArray信号接收sendArray信号(即接收随机数)然后进行冒泡排序,再发送finish信号
  • 主线程接收到finish信号后,将排序后的随机数显示在界面上

实现代码:

  • 子线程.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#pragma once
#include"qthread.h"
#include"qvector.h"
//新建随机数类
class myThread :
public QThread
{
Q_OBJECT
public:
myThread(QObject* parent = nullptr);
void scvnum(int num);//接收数字
protected:
void run();
signals:
void sendArray(QVector<int>num);//发送
private:
int m_num;
};

//新建冒泡排序类
class BubbleSort :
public QThread
{
Q_OBJECT
public:
BubbleSort(QObject* parent = nullptr);
void rcvArray(QVector<int>list);//要接收的是排序的数
protected:
void run();
signals:
void finish(QVector<int>list);//排序完成后发送一个finish信号
private:
QVector<int>m_list;
};
  • 子线程.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include "myThread.h"
#include"qelapsedtimer.h"
#include"qdebug.h"

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

}
BubbleSort::BubbleSort(QObject* parent) : QThread(parent)
{

}


void myThread::scvnum(int num)
{
m_num = num;
}

void myThread::run()
{
qDebug() << "child thread id" << QThread::currentThreadId();
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() << "The number of" << m_num << "generated";
qDebug() << "shared" << milsec << "second";
emit sendArray(list);
}

void BubbleSort::rcvArray(QVector<int> list)
{
m_list = list;
}

void BubbleSort::run()
{
qDebug() << "BubbleSort thread id:" << QThread::currentThreadId();
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() << "shared" << milsec << "second";
emit finish(m_list);
}
  • 主线程.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <QtWidgets/QWidget>
#include"ui_list.h"

class list : public QWidget
{
Q_OBJECT

public:
list(QWidget *parent = Q_NULLPTR);
signals:
void stating(int num);//设置要生成随机数个数

private:
Ui::listClass ui;
};
  • 主线程.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include "list.h"
#include"myThread.h"
list::list(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
//1.创建子线程对象
myThread* thread = new myThread();
BubbleSort* bub_thread = new BubbleSort();
//向子线程发送要生成的随机数个数
connect(this,&list::stating, thread, &myThread::scvnum);
//2启动子线程
connect(ui.pushButton, &QPushButton::clicked, [=]() {
emit stating(1000);//主线程设置子线程随机数的个数
thread->start();


});
connect(thread, &myThread::sendArray, bub_thread, &BubbleSort::rcvArray);

//3接收子线程发送的数据
connect(thread, &myThread::sendArray, [=](QVector<int>list) {
for (int i = 0; i < list.size(); ++i)
{
ui.randlist->addItem(QString::number(list.at(i)));
}
});
connect(thread, &myThread::sendArray, [=](QVector<int>list){
bub_thread->start();
});
connect(bub_thread, &BubbleSort::finish, [=](QVector<int>list) {
for (int i = 0; i < list. size(); ++i)
{
ui.bubblelist->addItem(QString::number(list.at(i)));
}
});
}

方法二:moveToThread()

思路:

  • 新建myThread类,用于生成随机数(working函数),在接受到主线程的信号后开始生成随机数
  • 新建BubbleSort类,用于排序(working函数),在接受到myThread类生成的随机数后开始排序
  • 最后显示在界面

代码实现:

  • 子线程.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#pragma once
#include"qthread.h"
#include"qvector.h"
#include"qobject.h"
//新建随机数类
class myThread :
public QObject
{
Q_OBJECT
public:
myThread(QObject* parent = nullptr);
void working(int num);//生成随机数
signals:
void sendArray(QVector<int>num);//发送
private:
int m_num;
};

//新建冒泡排序类
class BubbleSort :
public QObject
{
Q_OBJECT
public:
BubbleSort(QObject* parent = nullptr);
void working(QVector<int>list);//要接收的是排序的数

signals:
void finish(QVector<int>list);//排序完成后发送一个finish信号

};
  • 子线程.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include "myThread.h"
#include"qelapsedtimer.h"
#include"qdebug.h"

myThread::myThread(QObject* parent) :QObject(parent)
{

}
BubbleSort::BubbleSort(QObject* parent) : QObject(parent)
{

}


void myThread::working(int num)
{
qDebug() << "child thread id" << QThread::currentThreadId();
QVector<int> list;
QElapsedTimer time;
time.start();
for (int i = 0; i < num; i++)
{
list.push_back(qrand() % 100000);
}
int milsec = time.elapsed();
qDebug() << "The number of" << m_num << "generated";
qDebug() << "shared" << milsec << "second";
emit sendArray(list);
}


void BubbleSort::working(QVector<int>list)
{
qDebug() << "BubbleSort thread id:" << QThread::currentThreadId();
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() << "shared" << milsec << "second";
emit finish(list);
}
  • 主线程.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <QtWidgets/QWidget>
#include"ui_list.h"

class list : public QWidget
{
Q_OBJECT

public:
list(QWidget *parent = Q_NULLPTR);
signals:
void stating(int num);//设置要生成随机数个数

private:
Ui::listClass ui;
};
  • 主线程.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include "list.h"
#include"myThread.h"
list::list(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
//1.创建QThread对象
QThread* thread1 = new QThread;
QThread* thread2 = new QThread;
//2.创建子线程类对象
myThread* myth = new myThread();
BubbleSort* bub = new BubbleSort();
//3通过movetothread将子线程对象移动到QThread对象中
myth->moveToThread(thread1);
bub->moveToThread(thread2);
//4启动子线程

//先向子线程发送要生成的随机数个数
connect(this,&list::stating, myth, &myThread::working);
//再启动子线程
connect(ui.pushButton, &QPushButton::clicked, [=]() {
emit stating(1000);//主线程设置子线程随机数的个数
thread1->start();
});
//将生成好的随机数发送给BubbleSort类
connect(myth, &myThread::sendArray, bub, &BubbleSort::working);

//将生成好的随机数显示在界面
connect(myth, &myThread::sendArray, [=](QVector<int>list) {
for (int i = 0; i < list.size(); ++i)
{
ui.randlist->addItem(QString::number(list.at(i)));
}
});
//发送的同时启动排序算法
connect(myth, &myThread::sendArray, [=](QVector<int>list){
thread2->start();
});
//将排序好的数显示在界面
connect(bub, &BubbleSort::finish, [=](QVector<int>list) {
for (int i = 0; i < list. size(); ++i)
{
ui.bubblelist->addItem(QString::number(list.at(i)));
}
});
}

结论

通过对比,我们可以发现:

  • 由于第二种方法,我们可以自定义带参的子线程运行函数,因此代码更加简洁。
  • 在第二种方法中,我们还可以随意修改需要在哪个线程中运行,代码也更加灵活。
  • 第一种方法适合在线程中处理单一事件,其逻辑简单(只需要新建一个继承自QThread类的对象,重写run函数,然后启动即可),对于需要在一个线程中处理多个事件,还是用第二种方法比较好。
  • 为什么不能在第二种方法中,给定义的子线程对象添加父类呢? :由于添加了父类以后就不能再移动到QThread中去了

如何进行线程资源的释放?

  1. 在new对象时候,直接用this指定其父类(即放入对象数中)
  2. 在程序最后自行释放资源
1
2
3
4
5
6
7
8
9
10
connect(this, &list::destroyed, this, [=]() {
thread1->quit();
thread1->wait();
thread1->deleteLater();
thread2->quit();
thread2->wait();
thread2->deleteLater();
myth->deleteLater();
bub->deleteLater();
});



转载自:唯有自己强大 如有侵权,在下方评论 立刻删除。