QObject对象和线程
- 一、概述
- 二、QObjectReentrant性
- 三、每个线程事件的循环
- 四、从其他线程访问QObject的子类
- 五、跨线程的信号和槽函数
一、概述
QThread继承QObject。QThread它发出信号来指示线程开始或结束执行,还提供了一些任务槽。
Qobject可以在多个线程中使用,发出信号调用其他线程中的槽函数,并将事件发送给“活动”在其他线程中的对象。这是可能的,因为每个线程都允许有自己的事件循环。
二、QObjectReentrant性
QObject是 Reentrant 的。什么是Reentrant 可以参考 Reentrant 。
QObject的大多数非gui子类,如QTimer、QTcpSocket、QUdpSocket和QProcess,也是Reentrant的,这使得从多个线程同时使用这些类成为可能。注意,这些类被设计为在单个线程中创建和使用;在一个线程中创建对象并在另一个线程中调用它的函数并不能保证有效。有三个约束需要注意。
- QObject的子对象必须始终在父对象创建的线程中创建。这意味着,除了其他事情之外,永远不应该将QThread对象(This)作为在线程中创建的对象的父对象(因为QThread对象本身是在另一个线程中创建的)。
- 事件驱动对象只能在单个线程中使用。具体来说,这适用于定时器机制和网络模块。例如,不能在非对象所属线程的线程中启动定时器或连接套接字。
- 在删除QThread之前,必须确保在线程中创建的所有对象都被删除。这可以通过在run()实现中在栈上创建对象来轻松实现。
虽然QObject是Reentrant的,但GUI类,尤其是QWidget及其所有子类,是不Reentrant的。它们只能在主线程中使用。如前所述,QCoreApplication::exec()也必须从该线程调用。
实际上,在主线程之外的其他线程中使用GUI类是不可能的,通过将耗时的操作放在单独的工作线程中,并在工作线程完成时在主线程的屏幕上显示结果,可以很容易地解决这个问题。
一般来说,在QApplication之前创建qobject是不支持的,并且可能会在退出时导致奇怪的崩溃,这取决于平台。这意味着也不支持QObject的静态实例。一个结构合理的单线程或多线程应用程序应该让QApplication是第一个被创建,最后被销毁的QObject。
三、每个线程事件的循环
每个线程可以有自己的事件循环。初始线程使用QCoreApplication::exec()启动它的事件循环,或者对于单对话框GUI应用程序,有时使用QDialog::exec()。其他线程可以使用QThread::exec()启动事件循环。与QCoreApplication类似,QThread提供了一个exit(int)函数和一个quit()槽函数。
线程中的事件循环使得线程可以使用某些需要事件循环出现的非gui Qt类(如QTimer、QTcpSocket和QProcess)。它还可以将任何线程的信号连接到特定线程的槽函数。将在下面的跨线程信号和槽中更详细地解释。
QObject实例位于创建它的线程中。该对象的事件由该线程的事件循环分发。QObject所在的线程可以通过QObject::thread()获得。
QObject::moveToThread()函数更改对象及其子对象的线程亲和性(如果对象有父对象,则不能移动)。
如果不是从拥有该QObject的线程(或以其他方式访问该对象)上调用delete,则是不安全的,除非你保证该对象在那一刻没有处理事件。相反,使用QObject::deleteLater(),将发送一个DeferredDelete事件,对象线程的事件循环最终将接收该事件。默认情况下,拥有QObject的线程是创建QObject的线程,但在调用QObject::moveToThread()之后就不是这样了。
如果没有事件循环运行,事件就不会传递给对象。例如,如果您在线程中创建了一个QTimer对象,但从未调用exec(),那么QTimer将永远不会发出timeout()信号。调用deleteLater()也不能正常工作。(这些限制也适用于主线程。)
你可以使用线程安全函数QCoreApplication::postEvent()在任何时间手动向任何线程中的任何对象发送事件。事件将由创建对象的线程的事件循环自动分派。
所有线程都支持事件过滤器,但限制是监视对象必须与被监视对象在同一个线程中。类似地,QCoreApplication::sendEvent()(与postEvent()不同)只能用于将事件分发给函数调用所在线程中的对象。
四、从其他线程访问QObject的子类
QObject及其所有子类都不是线程安全的。这包括整个事件传递系统。重要的是要记住,当你从另一个线程访问对象时,事件循环可能会将事件传递给你的QObject子类。
如果在不在当前线程中的QObject子类上调用函数,并且该对象可能会接收事件,则必须使用互斥量保护对QObject子类内部数据的所有访问;否则,您可能会遇到崩溃或其他不希望出现的行为。
与其他对象一样,QThread对象存在于创建对象的线程中——而不是在调用QThread::run()时创建的线程中。在你的QThread子类中提供槽函数通常是不安全的,除非你用互斥量保护成员变量。
另一方面,你可以安全地从QThread::run()实现中发射信号,因为信号发射是线程安全的。
五、跨线程的信号和槽函数
Qt支持以下信号槽连接类型。
信号槽函数的一个参数会指定跨线程之类的,也就是最后一个默认参数 Qt::ConnectionType
QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)
参数选项及解释:
-
Auto Connection (默认):如果信号在接收对象具有父子关系的线程中发出,那么行为与直接连接相同。否则,行为与排队连接相同。”
-
Direct Connection:当信号发出时,立即调用槽函数。槽函数在发射器的线程中执行,而不一定是接收器的线程。
-
Queued Connection:当控制返回到接收方线程的事件循环时,调用槽函数。槽函数在接收者的线程中执行。
-
Blocking Queued Connection:排队连接与队列连接一样调用槽函数,但当前线程阻塞直到槽函数返回。
注意:使用此类型连接同一线程中的对象将导致死锁。 -
Unique Connection:连接行为与自动连接相同,但只有在不复制现有连接时才建立连接。也就是说,如果同一个信号已经连接到同一对对象的同一个槽函数,那么连接就不会建立,connect()返回false。
连接类型可以通过给connect()传递一个额外的参数来指定。请注意,如果事件循环在接收方的线程中运行,那么当发送方和接收方处于不同的线程中时,使用直接连接是不安全的,原因与在另一个线程中的对象上调用任何函数都是不安全的。
QObject::connect()本身是线程安全的。