一、如何理解多线程
二、实现多线程的两种方式(面向应用)
2.1 继承 QThread 的类
2.2 (推荐这种方式)函数 moveTothread()
三、多线程的释放问题(善后工作)
多线程的两种实现方法
- 一、如何理解多线程
- 二、实现多线程的两种方式(面向应用)
- 2.1 继承 QThread 的类
- 2.2 (推荐这种方式)函数 moveTothread()
- 三、多线程的释放问题(善后工作)
一、如何理解多线程
类似我们单片机的编程,如在 Keil5 中对 51 单片机或者 STM32 单片机进行编程时,如果我们使用模块化编程,那么 main.c
文件中可能代码如下:
#include "stm32f103c8t6.h"void main(){//初始化配置函数while(1){//控制函数,如点灯?}
}
从代码块中可以看出,我们写了一个 while 循环,目的是为了让程序一直运行,而如果去掉 while 循环,只点灯,可能灯就只会亮 1 下(可能人眼都看不到这一下)。而我们还会用到中断,中断就是打断主程序运行,去处理一下突然的内容(比如说外卖员给你打电话了,虽然你现在正在写代码,但是你接到了这个电话就去拿外卖了)
但是对于现在的电脑来说,中断是一种穿插执行任务的方式,多线程也是,这个可以理解为,我一边看动漫一边写代码,同时处理 2 件或以上的事情。
总结:多线程就是使程序能够同时处理多个任务
主线程
:理解为窗口线程
、UI 线程
、默认线程
,负责窗口事件处理或者窗口控件数据的更新
子线程
:负责后台处理一些内容,不能对窗口对象做任何操作,这些事情需要交给窗口线程处理,如果主线程需要与子线程进行信息的交互,需要用到 Qt 中的信号槽机制(子线程将信号发给主线程,然后主线程再进行调用)。
应用:当程序在处理一个内容时,用多余的资源处理其他力所能及的事
对串口调试助手来说,若要开启多个串口,哪就是创建多个串口对象,然后同时运行即可?(2024年1月5日试一下)
二、实现多线程的两种方式(面向应用)
QThread的两种实现.zip(学习测试 moveTothread 和基础 QThread 两种方法,第一次写)
TreadMethod.zip(这篇博客的完整代码)
2.1 继承 QThread 的类
继承 QThread 的方式实现多线程,实际上这是一种理解上简单,但是在实际应用中稍显复杂,并且程序越复杂,这个方法越笨拙不灵活。(在看教程的时候看到网上的人说,如果要实现网络 TCP 调试助手的话,非常不建议用继承 QThread 的类的方式来实现多线程)
这种方法,实际上是继承 QThread 的类,然后通过通过重写 run 函数来实现多线程。接下来做一个小栗子:
实例要求:打印三个线程 ID,包括主线程(UI 线程)ID、子线程 run 函数线程ID、子线程槽函数线程ID
细节:程序运行时先打印子线程 ID 和主线程 ID,然后通过发出信号,触发子线程槽函数线程 ID。
Step1
:新建一个类 thread_1
,选择继承 QObject,然后改成继承 QThread 的类
Step2
:重写 run 方法
Step3
:写一个子线程的槽函数
#ifndef THREAD_1_H
#define THREAD_1_H
#include <QThread>
#include <QDebug>
class thread_1 : public QThread
{
Q_OBJECT
public:
explicit thread_1(QThread *parent = nullptr);
signals:
protected:
void run() override{qDebug()<<"子线程ID:" <<QThread::currentThreadId()<<'\n';sleep(5);
};
public slots:
void thread1Slot(){qDebug()<<"子线程槽函数ID:" <<QThread::currentThreadId()<<'\n';
}
};
#endif // THREAD_1_H
step4
:在 UI 线程中打印线程 ID 和新建线程和初始化运行
step5
:链接(主线程)信号和(子线程)信号槽,并发射信号触发子线程信号槽打印其目前的线程ID
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include "thread_1.h"
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();private:Ui::MainWindow *ui;thread_1* myThread;
signals:void mainwindownSignal();};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);//新建一个子线程对象并让他开始运行myThread = new thread_1;myThread->start();//开始运行后会执行run函数,打印子线程的ID//执行打印主函数线程IDqDebug()<<"主线程ID:" <<QThread::currentThreadId()<<'\n';//链接(主线程)信号和(子线程)信号槽,并发射信号触发子线程信号槽打印其目前的线程IDconnect(this,&MainWindow::mainwindownSignal,myThread,&thread_1::thread1Slot);emit mainwindownSignal();
}
结果展示
:
① 结果让我有些意外,明明是让子线程先运行的,但是最先运行的是主线程,然后其次是子线程的槽函数,最后才是子线程的运行。
② 另外从图中可以看到,子线程的槽函数竟然处于主线程中!
2.2 (推荐这种方式)函数 moveTothread()
创建一个继承 QObject 的类(必须是继承 QObject),然后调用 moveTothread 方法,将这个继承 QObject 类的对象放到某个线程中,这样的好处是,可以让好几个对象放在同一个子线程中,而用方法一每次只能一个类来跑单一的线程,而且只能在 run 里跑,他子线程的槽函数还是运行在主线程中,想要实现多个线程就得继承多个 QThread 对象。
接下来做一个小栗子:
实例要求:打印三个线程 ID,包括主线程(UI 线程)ID、子线程 run 函数线程ID、子线程槽函数线程ID
细节:程序运行时先打印子线程 ID 和主线程 ID,然后通过发出信号,触发子线程槽函数线程 ID。
step1
:创建一个继承 QObject
的类 work
(与方法一类似,不展示了,为什么叫 work
?我觉得因为这是一个要做的事所以叫他 work
)
step2
:在这个类中写普通方法和槽函数
#ifndef WORK_H
#define WORK_H
#include <QObject>
#include <QDebug>
#include <QThread>
class work : public QObject
{Q_OBJECT
public:explicit work(QObject *parent = nullptr);void Working(){//写普通方法qDebug()<<"子线程ID:" <<QThread::currentThreadId()<<'\n';}
signals:
public slots:void WorkingSlot(){//写槽函数qDebug()<<"子线程ID:" <<QThread::currentThreadId()<<'\n';}
};
#endif // WORK_H
step3
:在 mainwindow.h
中创建 work
和 QThread
的对象:worker
和myThread
step4
:在 mainwindow.h
中写触发 worker
槽函数的信号mainwindownSignal
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include "work.h"
#include <QDebug>
#include <QThread>QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();private:
//step3:在 mainwindow.h 中创建 work 和 QThread 的对象:worker 和myThreadUi::MainWindow *ui;work* worker;QThread* myThread;
signals:
//step4:在 mainwindow.h 中写触发 worker 槽函数的信号mainwindownSignalvoid mainwindownSignal();
};
#endif // MAINWINDOW_H
step5
:在 mainwindow.c 中进行 moveTothread、start 以及信号槽的连接触发操作
#include "mainwindow.h"
#include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);worker = new work;myThread = new QThread;worker->moveToThread(myThread);//moveTothreadmyThread->start();//也要start才能运行//worker->Working();//让工人工作!看看是不是在子线程中处理!connect(this,&MainWindow::mainwindownSignal,worker,&work::WorkingSlot);connect(this,&MainWindow::mainwindownSignal,worker,&work::Working);emit mainwindownSignal();//发出信号,让worker中的槽函数开始执行看看是在哪个线程中执行qDebug()<<"主线程ID:" <<QThread::currentThreadId()<<'\n';
}MainWindow::~MainWindow()
{delete ui;
}
结果展示
:
① 通过信号的方式触发操作子线程让其运行是在子线程中运行的。(但是如果直接调用 working 方法,还是在主线程中,L13
去掉注释看看)
三、多线程的释放问题(善后工作)
QThread 对象不能挂载在对象树上进行自动释放,会出问题,手动释放比较好。
第二部分介绍了怎么实现,但是对于多线程的资源释放,这里也简单总结一下,原理我也不是很擅长说。
connect(mComThread,&QThread::finished, this,&QObject::deleteLater);
参考教程:
Qt 教程-爱编程的大丙
Qt 串口多线程 继承QThread_pyqt串口多线程-CSDN博客
QThread的用法-CSDN博客