事件循环详细解释可以点击该链接:Qt实用技能3-理解事件循环 - 知乎 (zhihu.com)
子类化QThread的事件循环:
事件循环的作用:
在run()函数中添加事件循环后,run()函数中的内容执行完后,线程还会运作,启动事件循环后,主线程还可以和子线程通过信号与槽的方式继续使用。
- 使用exec()来启动事件循环
- exec()后面的内容将不会运行,直到退出事件循环
- 使用quit()退出事件循环
- 只有槽函数所在线程开启了事件循环,它才能在对应信号发射后被调用
- 无论事件循环是否开启,信号发送后会直接进入槽函数所依附的线程的事件队列
首先要了解一下connect中的槽函数所依附的线程:
线程: My_thread.cpp文件
#include "my_thread.h"
#include
My_thread::My_thread(QObject *parent) : QThread(parent)
{
}
void My_thread::run()//重写run函数
{
qDebug()<<"run中的线程号&#xff1a;"< exec();//开启事件循环
}
void My_thread::show_Slot_place()//槽函数
{
qDebug()<<"槽函数的线程号"<}
主类&#xff1a;Widget.cpp文件
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
qDebug()<<"主线程的位置&#xff1a;"< My_thread *thread&#61;new My_thread;
connect(this,&Widget::show_Slot,thread,&My_thread::show_Slot_place);
thread->start();//开启线程
emit show_Slot();//触发信号
}
![](https://img.php1.cn/3cd4a/1eebe/cd5/617c1173853af4b6.webp)
上面的数据可以看出&#xff1a;该槽函数是在主线程中执行的
如何让槽函数在run&#xff08;&#xff09;函数运行&#xff1a;
- 在线程类中的构造函数中添加 movetoThread&#xff08;this&#xff09;
在线程类中的构造函数中添加 movetoThread&#xff08;this&#xff09;
创建对象时&#xff0c;把自己依附到次线程中。
线程&#xff1a; My_thread.cpp文件
![](https://img.php1.cn/3cd4a/1eebe/cd5/7d7ef3f69d479716.webp)
![](https://img.php1.cn/3cd4a/1eebe/cd5/8be1ccb5166feb93.webp)
子类化QThread退出线程的方法&#xff1a;
子类化QThread释放线程内存的方法&#xff1a;
- 有父类的话&#xff0c;在父类的析构函数中使线程执行完毕&#xff0c;然后会跟着父类释放内存
- 无父类的话&#xff0c;使用deleteLater()函数销毁
使用子类化QObject&#xff0c;使用movetoThread(thread)
使用这种方法的话&#xff0c;默认拥有事件循环&#xff0c;且处在事件循环中。
子类object&#xff1a;
#include "object.h"
#include
object::object(QObject *parent) : QObject(parent)
{
}
void object::show_Slot()
{
qDebug()<<"槽函数的线程&#xff1a;"<}
主类&#xff1a;
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QThread *thread&#61;new QThread;
object * object1&#61;new object;
qDebug()<<"主线程号&#xff1a;"< object1->moveToThread(thread);
connect(this,&Widget::show_Slots,object1,&object::show_Slot);
thread->start();
emit this->show_Slots();
}
![](https://img.php1.cn/3cd4a/1eebe/cd5/0ef126b5295c089b.webp)
这种方法&#xff0c;比子类化QThread更方便的在子线程中执行槽函数
释放资源的方法&#xff1a;
使用deleteLater&#xff08;&#xff09;函数释放资源&#xff0c;释放资源会变成野指针&#xff0c;所以还要把指针置空。
//释放堆空间资源
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(m_th) &#61;&#61; sender){
m_th &#61; nullptr;
qDebug("set m_th &#61; nullptr");
}
if(qobject_cast(m_obj) &#61;&#61; sender){
m_obj &#61; nullptr;
qDebug("set m_obj &#61; nullptr");
}
}
线程和 QObjects
QThread继承了QObject&#xff0c;QObject可以在多个线程中使用&#xff0c;发出调用其他线程中槽的信号&#xff0c;并将事件发布到在其他线程中“活动”的对象。这是可能的&#xff0c;因为允许每个线程都有自己的事件循环。
QObject是可以重入的&#xff0c;大多数非GUI子类都是可重入的。
QTimer&#xff0c;QTcpSocket&#xff0c;QUdpSocket&#xff0c;QProcess&#xff0c;在多线程中使用这些类时&#xff1a;
- 必须始终在创建父类的线程中创建QObject的子级
- 事件驱动对象只能在单个线程中使用
- 再删除QThread之前&#xff0c;必须确保删除线程中创建的所有对象。
通常&#xff0c;不支持在 QApplication 之前创建 QObjects&#xff0c;这可能会导致退出时出现奇怪的崩溃&#xff0c;具体取决于平台。这意味着 QObject 的静态实例也不受支持的。结构合理的单线程或多线程应用程序应使 QApplication 成为第一个创建&#xff0c;最后一个销毁的 QObject。
线程的事件循环&#xff1a;
每个线程都可以有自己的事件循环&#xff0c;线程中的事件循环使线程可以使用某些需要存在事件循环的非 GUI Qt 类&#xff08;例如 QTimer、QTcpSocket 和 QProcess&#xff09;。它还可以将来自任何线程的信号连接到特定线程的插槽。
从其他线程访问QObject子类&#xff1a;
QObject和其所有子类都不是线程安全的&#xff0c;当您从另一个线程访问对象时&#xff0c;事件循环可能会将事件传递到QObject子类。如果要在当前线程中不存在的 QObject 子类上调用函数&#xff0c;并且该对象可能会接收事件&#xff0c;则必须使用互斥锁保护对 QObject 子类内部数据的所有访问;否则&#xff0c;您可能会遇到崩溃或其他不良行为。
注意&#xff1a;QThread对象创建于对象线程中&#xff0c;而run&#xff08;&#xff09;函数再子线程中&#xff0c;所以在QThread子类中提供插槽通常是不安全的&#xff0c;需要使用互斥锁保护成员变量。但从run&#xff08;&#xff09;函数中发射信号是线程安全的。
跨线程的信号和插槽&#xff1a;
信号的连接方式有&#xff1a;
Qt&#xff1a;&#xff1a;AutoConnection&#xff08;默认&#xff09; | 接收对象有关联的线程中发出信号&#xff0c;直接连接。 否则&#xff0c;排队连接 |
Qt&#xff1a;&#xff1a;DirectConnec | 发出信号时&#xff0c;将立即调用该插槽。该插槽在信令线程中执行 |
Qt::QueuedConnection | 当控制返回到接收器线程的事件循环时&#xff0c;将调用该槽。该插槽在接收器的线程中执行。 |
Qt::BlockingQueuedConnection | 与 Qt&#xff1a;&#xff1a;QueuedConnection 相同&#xff0c;不同之处在于信令线程阻塞&#xff0c;直到插槽返回。如果接收器位于信令线程中&#xff0c;则不得使用此连接&#xff0c;否则应用程序将死锁。 |
Qt::UniqueConnection | 这是一个标志&#xff0c;可以使用按位 OR 与上述任何一种连接类型结合使用。当设置Qt&#xff1a;&#xff1a;UniqueConnection时&#xff0c;如果连接已经存在&#xff08;即&#xff0c;如果同一信号已经连接到同一对对象的同一插槽&#xff09; |
直接连接的情况&#xff1a;
当发射信号的线程和槽函数槽函数的线程相同时&#xff1a;
这种情况的弊端在于&#xff0c;主线程可以访问该函数&#xff0c;run函数也可以访问该函数&#xff0c;需要使用互斥锁来同步数据。
线程&#xff1a; My_thread.cpp文件
#include "my_thread.h"
#include
My_thread::My_thread(QObject *parent) : QThread(parent)
{
}
void My_thread::run()//重写run函数
{
qDebug()<<"run中的线程号&#xff1a;"< exec();//开启事件循环
}
void My_thread::show_Slot_place()//槽函数
{
qDebug()<<"槽函数的线程号"<}
主类&#xff1a;Widget.cpp文件
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
qDebug()<<"主线程的位置&#xff1a;"< My_thread *thread&#61;new My_thread;
connect(this,&Widget::show_Slot,thread,&My_thread::show_Slot_place);//直接连接
thread->start();//开启线程
emit show_Slot();//触发信号
}
![](https://img.php1.cn/3cd4a/1eebe/cd5/617c1173853af4b6.webp)
当槽函数和线程和run&#xff08;&#xff09;函数相同时&#xff1a;
- 在上面例子中的构造函数添加 movetoThread(this)&#xff08;不建议使用&#xff09;
- 使用继承QObject &#xff0c;然后调用moveThread&#xff08;thread&#xff09;的方式创建
例子在文章开头已经介绍了&#xff0c;这里就不重复介绍了。
同步线程和异步线程&#xff1a;
同步线程&#xff1a;
线程对象主动等待线程生命期结束后才销毁&#xff0c;线程对象销毁时确保线程执行结束&#xff0c;支持在栈或堆上创建线程对象。
在线程类的析构函数中先调用wait函数&#xff0c;强制等待线程执行结束。
使用场合&#xff1a;适用于线程生命期较短的场合
异步线程&#xff1a;
线程生命期结束时通知线程对象销毁。
只能在堆空间创建线程对象&#xff0c;线程对象不能被外界主动销毁。
在run函数中最后调用deleteLater()函数。
线程函数主动申请销毁线程对象。
使用场合&#xff1a;
线程生命期不可控&#xff0c;需要长时间运行于后台的线程。
参考文章&#xff1a;
Qt 的线程与事件循环_FreyrLin的博客-CSDN博客
【QT】子类化QThread实现多线程_李春港的博客-CSDN博客
QT多线程编程详解_coolboywjun的博客-CSDN博客_qt 多线程