往期回顾
【QT进阶】Qt http编程之websocket的简单介绍-CSDN博客
【QT进阶】Qt线程与并发之创建线程的三种方法(超详细介绍)
Qt创建线程的方法不少,这里主要看常用的几种,要注意两个重点:
1、要学会每种创建线程方式并熟练使用
2、要能够区分每种线程创建后的主线程和子线程
一、派生于QThread
1、具体思路
创建一个类Thread01,派生于QThread,这是Qt创建线程最常用的方法,需要我们重写虚函数void_QThread:run(),在run方法里写具体的内容,外部通过Thread01的实例化对象去调用start方法,即可执行线程体run()。
2、区分线程
派生于QThread的类,构造函数属于主线程,run函数属于子线程,可以通过currentThreadId()方法打印线程id判断。
3、示例
3.1Thread01.h
#pragma once
#include <QThread>
class Thread01 : public QThread
{
Q_OBJECT
public:
Thread01();
void run() override;
};
3.2Thread01.cpp
#include "Thread01.h"
#include <QDebug>
Thread01::Thread01()
{
qDebug() << "Thread01 construct " << QThread::currentThreadId();
}
void Thread01::run()
{
qDebug() << "Thread01 run " << QThread::currentThreadId();
int index = 0;
while (1)
{
qDebug() << index++;
QThread::msleep(500); //加一个延迟执行,500毫秒
}
}
3.3main.cpp
#include <QtCore/QCoreApplication>
#include <iostream>
#include "Thread01.h"
using namespace std;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
cout << "main thread" << QThread::currentThreadId() << endl;
Thread01 th;
th.start();
cout << "main thread end" << endl;
return a.exec();
}
3.4运行结果
通过结果可以看到,确实是构造函数属于主线程,run函数属于子线程。
二、派生于QRunnable
1、具体思路
创建一个类Thread02,派生于QRunnable,重写run()方法,在run方法里处理其它任务,不同的是,调用时不再是start方法,而是需要借助Qt线程池QThreadPool
MyThread *pTh = new MyThread();
QThreadPool:globallnstance()->start(pTh);
注意:
这种新建线程的方法的最大的缺点就是:不能使用Qt的信号槽机制,因为QRunnable不是继承自Q0bject。但是这种方法的好处就是,可以让QThreadPool来管理线程,QThreadPool会
自动的清理我们新建的QRunnable对象。
2、区分线程
同样的,派生于QThread的类,构造函数属于主线程,run函数属于子线程,可以通过currentThreadId()方法打印线程id判断。
3、示例
3.1Thread02.h
#pragma once
#include <QRunnable>
class Thread02 : public QRunnable
{
public:
Thread02();
~Thread02();
void run() override;
};
3.2Thread02.cpp
#include "Thread02.h"
#include <QThread>
#include <QDebug>
Thread02::Thread02()
{
qDebug() << "Thread02 construct " << QThread::currentThreadId();
}
Thread02::~Thread02()
{
qDebug() << "Thread02 xigou func";
}
void Thread02::run()
{
qDebug() << "Thread02 run " << QThread::currentThreadId();
}
3.3main.cpp
#include <QtCore/QCoreApplication>
#include <iostream>
#include "Thread02.h"
#include <QThreadPool>
using namespace std;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
cout << "main thread" << QThread::currentThreadId() << endl;
Thread02 *th = new Thread02();
QThreadPool::globalInstance()->start(th);
cout << "main thread end" << endl;
return a.exec();
}
3.4运行结果
同样满足构造函数属于主线程,run函数属于子线程,可以看到我们并没有手动析构,但是关闭主线程的时候自动调用了子线程的析构函数。
三、moveToThread
1、具体思路
创建一个类Thread03,派生于QObject,使用moveToThread方法将QThread对象作为私有成员,在构造函数里moveToThread,然后调用start方法启动线程。
this->moveToThread(&m_th);
m_th.start(); .
2、示例
我们一步步来,通过三种方式讲解这个点
2.1直接在槽函数里执行代码
直接在点击按钮的槽函数里执行代码,实现无限循环并每次暂停300毫秒,这会使得同一线程下的窗口非常卡
#include "ch71_moveToThread.h"
#include <QDebug>
ch71_moveToThread::ch71_moveToThread(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
qDebug() << "main construct " << QThread::currentThreadId();
m_pTh03 = new Thread03();
//connect(this, &ch71_moveToThread::sig_fun, m_pTh03, &Thread03::fun);
}
ch71_moveToThread::~ch71_moveToThread()
{
}
void ch71_moveToThread::on_pushButton_clicked()
{
//m_pTh03->fun();
//emit sig_fun();
int index = 0;
while (1)
{
qDebug() << index++;
QThread::msleep(300);
}
}
2.1.1运行结果
此时由于点击按钮后无限循环并每次暂停300毫秒,窗口已经变得非常卡无法拖动了
2.2在槽函数里执行fun方法
在点击按钮的槽函数里调用fun方法,实现无限循环并每次暂停300毫秒,这会使得同一线程下的窗口非常卡
#include "ch71_moveToThread.h"
#include <QDebug>
ch71_moveToThread::ch71_moveToThread(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
qDebug() << "main construct " << QThread::currentThreadId();
m_pTh03 = new Thread03();
//connect(this, &ch71_moveToThread::sig_fun, m_pTh03, &Thread03::fun);
}
ch71_moveToThread::~ch71_moveToThread()
{
}
void ch71_moveToThread::on_pushButton_clicked()
{
m_pTh03->fun();
//emit sig_fun();
/*int index = 0;
while (1)
{
qDebug() << index++;
QThread::msleep(300);
}*/
}
2.2.1 运行结果
发现此时main函数里,Thread03的构造函数里,Thread03的fun函数里,三个是同一个线程,所以此时由于无限循环并每次暂停300毫秒,窗口已经变得非常卡无法拖动了。
2.3 在槽函数里发送信号用信号槽机制
用信号槽机制,在点击按钮的槽函数里发送信号,再在moveToThread构造函数里用信号槽接受信号并调用fun方法,此时由于窗口与fun方法是两个不同线程在运行,所以窗口并不卡
#include "ch71_moveToThread.h"
#include <QDebug>
ch71_moveToThread::ch71_moveToThread(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
qDebug() << "main construct " << QThread::currentThreadId();
m_pTh03 = new Thread03();
connect(this, &ch71_moveToThread::sig_fun, m_pTh03, &Thread03::fun);
}
ch71_moveToThread::~ch71_moveToThread()
{
}
void ch71_moveToThread::on_pushButton_clicked()
{
emit sig_fun();
}
2.3.1运行结果
点击窗口中的按钮时,会触发 on_pushButton_clicked() 槽函数。on_pushButton_clicked() 槽函数会发射 sig_fun 信号。sig_fun 信号连接到 Thread03 对象的 fun() 槽函数。调用 fun() 槽函数后,会在 Thread03 的线程中执行一个无限循环,输出索引并每次暂停300毫秒。
3、提问
问:为什么在按钮的槽函数里发送信号用信号槽的方式调用fun函数,最后窗口和fun函数就是在不同线程运行,而如果我直接在按钮的槽函数里调用fun方法,就是在同一线程下运行而导致窗口会卡顿?
答:
在Qt中,GUI操作通常是在主线程(也称为UI线程)中执行的,称为GUI线程。如果在GUI线程中执行耗时的操作,比如执行一个循环或者其他耗时操作,会导致界面卡顿,因为GUI线程被阻塞,无法及时响应用户输入和更新界面。
通过使用信号槽机制,可以将耗时操作放在另一个线程中执行,从而避免阻塞GUI线程。在我们的代码中,通过将Thread03对象的fun()函数连接到信号sig_fun上,当按钮点击时发射sig_fun信号,触发fun()函数在Thread03对象所在的线程中执行,而不是在GUI线程中执行。
如果直接在按钮的槽函数中调用 fun() 方法,那么 fun() 方法会在GUI线程中执行,导致界面卡顿,因为在 fun() 方法中有一个无限循环,每次暂停300毫秒。这会阻塞GUI线程无法及时响应用户输入和刷新界面,导致界面卡顿。
所以说:通过使用信号槽机制,将耗时操作放在另一个线程中执行,可以保持界面的流畅性和响应性。
以上就是Qt里创建线程的三种方法的简单介绍
都看到这里了,点个赞再走呗朋友~
加油吧,预祝大家变得更强!