Bootstrap

QThread 事件循环

事件循环详细解释可以点击该链接Qt实用技能3-理解事件循环 - 知乎 (zhihu.com) 

子类化QThread的事件循环: 

事件循环的作用:

在run()函数中添加事件循环后,run()函数中的内容执行完后,线程还会运作,启动事件循环后,主线程还可以和子线程通过信号与槽的方式继续使用。

  1. 使用exec()来启动事件循环
  2. exec()后面的内容将不会运行,直到退出事件循环
  3. 使用quit()退出事件循环
  4. 只有槽函数所在线程开启了事件循环,它才能在对应信号发射后被调用
  5. 无论事件循环是否开启,信号发送后会直接进入槽函数所依附的线程的事件队列

首先要了解一下connect中的槽函数所依附的线程:

线程: My_thread.cpp文件

#include "my_thread.h"
#include<QDebug>
My_thread::My_thread(QObject *parent) : QThread(parent)
{

}
void My_thread::run()//重写run函数
{
    qDebug()<<"run中的线程号:"<<currentThreadId();
    exec();//开启事件循环
}
void My_thread::show_Slot_place()//槽函数
{
    qDebug()<<"槽函数的线程号"<<currentThreadId();
}

主类:Widget.cpp文件

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    qDebug()<<"主线程的位置:"<<QThread::currentThreadId();
    My_thread *thread=new My_thread;
    connect(this,&Widget::show_Slot,thread,&My_thread::show_Slot_place);
    thread->start();//开启线程
    emit show_Slot();//触发信号
}

上面的数据可以看出:该槽函数是在主线程中执行的

如何让槽函数在run()函数运行:

  • 在线程类中的构造函数中添加  movetoThread(this)

在线程类中的构造函数中添加  movetoThread(this)

创建对象时,把自己依附到次线程中。

 线程: My_thread.cpp文件

子类化QThread退出线程的方法: 

  • 无事件循环:run函数中有while循环的话,设置一个退出条件即可。

  • 有事件循环:先退出while循环,再quit()退出消息队列,wait()等待线程执行完毕

 子类化QThread释放线程内存的方法:

  • 有父类的话,在父类的析构函数中使线程执行完毕,然后会跟着父类释放内存
  • 无父类的话,使用deleteLater()函数销毁

使用子类化QObject,使用movetoThread(thread)

使用这种方法的话,默认拥有事件循环,且处在事件循环中。

子类object:

#include "object.h"
#include<QThread>
object::object(QObject *parent) : QObject(parent)
{

}
void object::show_Slot()
{

    qDebug()<<"槽函数的线程:"<<QThread::currentThreadId();

}

主类:

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QThread *thread=new QThread;
    object * object1=new object;

    qDebug()<<"主线程号:"<<QThread::currentThreadId();
    object1->moveToThread(thread);
    connect(this,&Widget::show_Slots,object1,&object::show_Slot);
    thread->start();
    emit this->show_Slots();
}

 这种方法,比子类化QThread更方便的在子线程中执行槽函数

 释放资源的方法:

使用deleteLater()函数释放资源,释放资源会变成野指针,所以还要把指针置空。

//释放堆空间资源
connect(m_th, &QThread::finished, m_obj, &QObject::deleteLater);
connect(m_th, &QThread::finished, m_th, &QObject::deleteLater);
//设置野指针为nullptr
connect(m_th, &QObject::destroyed, this, &MainWindow::SetPtrNullptr);
connect(m_obj, &QObject::destroyed, this, &MainWindow::SetPtrNullptr);

//消除野指针
void SetPtrNullptr(QObject *sender){
    if(qobject_cast<QObject*>(m_th) == sender){
        m_th = nullptr;
        qDebug("set m_th = nullptr");
    }

    if(qobject_cast<QObject*>(m_obj) == sender){
        m_obj = nullptr;
        qDebug("set m_obj = nullptr");
    }
}

线程和 QObjects

QThread继承了QObject,QObject可以在多个线程中使用,发出调用其他线程中槽的信号,并将事件发布到在其他线程中“活动”的对象。这是可能的,因为允许每个线程都有自己的事件循环。

QObject是可以重入的,大多数非GUI子类都是可重入的。

QTimer,QTcpSocket,QUdpSocket,QProcess,在多线程中使用这些类时:

  • 必须始终在创建父类的线程中创建QObject的子级
  • 事件驱动对象只能在单个线程中使用
  • 再删除QThread之前,必须确保删除线程中创建的所有对象。

通常,不支持在 QApplication 之前创建 QObjects,这可能会导致退出时出现奇怪的崩溃,具体取决于平台。这意味着 QObject 的静态实例也不受支持的。结构合理的单线程或多线程应用程序应使 QApplication 成为第一个创建,最后一个销毁的 QObject。

线程的事件循环:

每个线程都可以有自己的事件循环,线程中的事件循环使线程可以使用某些需要存在事件循环的非 GUI Qt 类(例如 QTimer、QTcpSocket 和 QProcess)。它还可以将来自任何线程的信号连接到特定线程的插槽。

从其他线程访问QObject子类:

QObject和其所有子类都不是线程安全的,当您从另一个线程访问对象时,事件循环可能会将事件传递到QObject子类。如果要在当前线程中不存在的 QObject 子类上调用函数,并且该对象可能会接收事件,则必须使用互斥锁保护对 QObject 子类内部数据的所有访问;否则,您可能会遇到崩溃或其他不良行为。

注意:QThread对象创建于对象线程中,而run()函数再子线程中,所以在QThread子类中提供插槽通常是不安全的,需要使用互斥锁保护成员变量。但从run()函数中发射信号是线程安全的。

跨线程的信号和插槽:

信号的连接方式有:

Qt::AutoConnection(默认)

接收对象有关联的线程中发出信号,直接连接。

否则,排队连接

Qt::DirectConnec发出信号时,将立即调用该插槽。该插槽在信令线程中执行
Qt::QueuedConnection当控制返回到接收器线程的事件循环时,将调用该槽。该插槽在接收器的线程中执行。
Qt::BlockingQueuedConnection与 Qt::QueuedConnection 相同,不同之处在于信令线程阻塞,直到插槽返回。如果接收器位于信令线程中,则不得使用此连接,否则应用程序将死锁。
Qt::UniqueConnection这是一个标志,可以使用按位 OR 与上述任何一种连接类型结合使用。当设置Qt::UniqueConnection时,如果连接已经存在(即,如果同一信号已经连接到同一对对象的同一插槽)

直接连接的情况:

当发射信号的线程和槽函数槽函数的线程相同时:

这种情况的弊端在于,主线程可以访问该函数,run函数也可以访问该函数,需要使用互斥锁来同步数据。

 线程: My_thread.cpp文件

#include "my_thread.h"
#include<QDebug>
My_thread::My_thread(QObject *parent) : QThread(parent)
{

}
void My_thread::run()//重写run函数
{
    qDebug()<<"run中的线程号:"<<currentThreadId();
    exec();//开启事件循环
}
void My_thread::show_Slot_place()//槽函数
{
    qDebug()<<"槽函数的线程号"<<currentThreadId();
}

主类:Widget.cpp文件

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    qDebug()<<"主线程的位置:"<<QThread::currentThreadId();
    My_thread *thread=new My_thread;
    connect(this,&Widget::show_Slot,thread,&My_thread::show_Slot_place);//直接连接
    thread->start();//开启线程
    emit show_Slot();//触发信号
}

 当槽函数和线程和run()函数相同时:

  • 在上面例子中的构造函数添加 movetoThread(this)(不建议使用)
  • 使用继承QObject ,然后调用moveThread(thread)的方式创建

例子在文章开头已经介绍了,这里就不重复介绍了。 

 

同步线程和异步线程:

同步线程:

线程对象主动等待线程生命期结束后才销毁,线程对象销毁时确保线程执行结束,支持在栈或堆上创建线程对象。

在线程类的析构函数中先调用wait函数,强制等待线程执行结束。

使用场合:适用于线程生命期较短的场合

异步线程:

线程生命期结束时通知线程对象销毁。

只能在堆空间创建线程对象,线程对象不能被外界主动销毁。

在run函数中最后调用deleteLater()函数。

线程函数主动申请销毁线程对象。

使用场合:

线程生命期不可控,需要长时间运行于后台的线程。

 

 参考文章:

Qt 的线程与事件循环_FreyrLin的博客-CSDN博客

【QT】子类化QThread实现多线程_李春港的博客-CSDN博客

 QT多线程编程详解_coolboywjun的博客-CSDN博客_qt 多线程 

;