七、Qt多线程之QThread

Qt多线程详解之QThread

关于多线程这个话题,相信大部分人都比较熟悉,但是要想深入理解多线程本质的话,多半得借助MCU以及RTOS这样的片上系统,今天就不深入展开,我们放在下一篇分析。从使用的角度,线程真的很棒很好用,特别是Qt库对多线程的封装,使得线程使用起来更加优雅,更少的代码和更简单的使用方式,今天就来详细分析qt的线程的使用方式以及应用场景等等
在这里插入图片描述

一、继承QThread重写run()

这种方式是最简单的,几乎在90%以上的场景都会用这个,非常适用于那种需要长期存在,也就是常驻内存的线程使用场景,使用非常简单,新建一个Mythread类,继承QThread,重写virtual void run();,然后调用继承过来的共有函数start(),线程就会开始运行,非常简单,下面看代码

//类定义
class CollectorThread : public QThread
{
    Q_OBJECT
public:
   CollectorThread(CthreadInterface *ptr);
signals:

public slots:

public:
    void test() {
        while(1)
        {
            
        }
    }
protected:
    void run() {//在cpp文件中,这个run函数一般是一个while循环
        while(true) {
            if(flag) {
                //这里就实现所有的线程要做的事情,这个falg一般用于线程的工作的启动和停止的控制,也可以调用exec()阻塞,等待事件唤醒
            }
        }
    }
private:
    bool flag;
    int currpoint = 0;
    CthreadInterface *controlptr;
};

下面才是重点,大家有时候可能分不清线程之间的区别,假设有一个这样的场景,有一个按钮,按钮的槽函数中,需要间隔1秒打印5次,但是根据我们实际测试,如果在槽函数直接调用,类似下面这样

void on_pushButton_clicked() {
        for(int i = 0; i < 5; i++) {
            qDebug() << "my is main thread" << QThread::currentThreadId();
            QThread::sleep(1);
        }
}
//这样做会导致界面阻塞,不可点击拖动,也就是常见的卡死现象
  • 重点重点重点

这时候说,我们写一个线程类,继承Qthread,来执行这样的阻塞操作,可不可以呢,答案肯定是可以,但是有一个很大的误区,要分清楚主线程和子线程,以及当前函数到底在哪个线程中运行,有很多关于信号和槽又涉及多线程的时候,很多莫名其妙的错误,都是由于这个概念不清晰导致的

看下面的代码,由于篇幅有限,我将某些函数的实现也放在类中了,这是一个Qt默认生成的mainwindow界面,为了放在一起清晰,我直接两个类放在一起了

#include <QMainWindow>
#include <QThread>
#include <QDebug>
class TestTHread : public QThread//继承QThread
{
    Q_OBJECT
public:
    TestTHread(){}
    void run(){//重写run,当调用start()时,就会调用这个函数
        while(1) {
            qDebug() << "1 my is run threadid" << QThread::currentThreadId();
            QThread::sleep(10);
        }
    }
    void test() {//这个函数,如果在run()中调用,就没有问题,但是在按钮槽函数中调用,界面同样会卡死
        while(1) {
            qDebug() << "2 my is test threadid" << QThread::currentThreadId();
            sleep(10);
        }
    }
};
namespace Ui {
class MainWindow;
}
//*****************************************************************************************
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit MainWindow()
    {
        thread.start();//界面的构造函数,会启动成员变量中创建的线程
    }
    ~MainWindow();
private slots:
    void on_pushButton_clicked() {
        thread.test();
        qDebug() << "3 my is main thread" << QThread::currentThreadId();
    }
private:
    TestTHread thread;//上面的自定义线程对象
    Ui::MainWindow *ui;
};
//上面的代码一运行,我们注意看打印结果,由于贴图不方便,我直接将运行结果复制过来,大家注意看3条线程编号的提示信息
1 my is run thread 0x1a0c
2 my is run thread 0x12d8
3 my is main thread 0x12d8

  • 重点:我们发现,只有run()函数中打印的线程ID是0x1a0c,而按钮的槽函数中的是0x12d8,按钮的槽函数中又调用了线程对象中的test()函数,test()函数是属于TestTHread这个类中的函数,也就是线程对象中的函数,但是test()和槽函数中打印的线程ID一样
  • 结论:当我们自己重写一个类,继承QThread,重写run()函数的时候,只有run()函数属于子线程,而这个类中的其他函数,如果在外部调用,还是属于在主线程中运行,也就表明,所有的阻塞操作,必须要在run()中完成,不然一样会导致界面卡死的现象

关于某个函数到底是在主线程运行,还是在子线程运行这个问题,其实从更底层的编译原理和C/C++语言的运行机制来看,函数其实并没有属于主线程还是子线程之分,代码被编译成汇编后,每个函数会获得一个标号,也可以理解为函数起始地址,判断一个函数到底在子线程还是主线程,跟函数本身没有关系,而是跟调用者有关系 , run()函数中调用的函数,就属于子线程,所以判别方式就是,看调用者,而不是看函数所处位置,子线程中调用的函数就属于子线程,主线程调用就属于主线程,下面我给大家详细分析一下

run()函数为什么会在子线程中呢?是因为当我们创建线程的时候,会在内存中分配一段单独的空间,代码只有一份,当调用start()的时候,就会去从run()这个函数的起始地址读取机器码,一条指令一条指令的读取运行,这个函数中的局部变量、以及函数调用,会被进行压栈,这个栈中就记录着当前线程运行的位置,也就是代码的位置,到底是运行到哪一条了呀,如果发生线程调用切换,就从这个专属的栈空间中弹栈,恢复代码运行位置、临时变量等等。因为子线程中调用的函数,局部变量,返回地址等也同样会被压栈进入子线程栈,反之主线程也一样,所以谁调用函数,函数就属于调用者所在线程。

所以如果有涉及阻塞的操作,一定要注意区分,阻塞操作到底是在哪里执行的,值得一提的是,信号和槽,是否在同一个线程,如果有这样的场景:在线程中发送信号,也就是在run()中发送信号,而响应函数在主线程中,这种就属于跨线程传输,要注意connect()是有第五个参数的,也就是如果发生跨线程的信号和槽,在哪里运行槽函数以及等待方式的问题。关于这个问题就不展开讨论,后续会详细分析。本篇就到这。

其实在学习的时候,大多数的场合都是最为简单的demo,没有这么复杂,但是在实际应用中,往往比这复杂得多,只有实际踩坑才会去注意这些具体应用的细节问题,关于线程的退出,等待并未过多提及,那个资料很多了,就不说了,贴个图

// 构造函数
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);
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值