【QT进阶】Qt线程与并发之创建线程的三种方法(超详细介绍)

往期回顾

【QT进阶】Qt http编程之websocket的简单介绍-CSDN博客

【QT进阶】Qt http编程之实现websocket server服务器端-CSDN博客

【QT进阶】Qt http编程之实现websocket client客户端-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里创建线程的三种方法的简单介绍

都看到这里了,点个赞再走呗朋友~

加油吧,预祝大家变得更强!

  • 56
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值