事件循环详细解释可以点击该链接:Qt实用技能3-理解事件循环 - 知乎 (zhihu.com)
子类化QThread的事件循环:
事件循环的作用:
在run()函数中添加事件循环后,run()函数中的内容执行完后,线程还会运作,启动事件循环后,主线程还可以和子线程通过信号与槽的方式继续使用。
- 使用exec()来启动事件循环
- exec()后面的内容将不会运行,直到退出事件循环
- 使用quit()退出事件循环
- 只有槽函数所在线程开启了事件循环,它才能在对应信号发射后被调用
- 无论事件循环是否开启,信号发送后会直接进入槽函数所依附的线程的事件队列
首先要了解一下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博客