本页内容

线程与 QObject

QThread 继承自QObject 。它会发出信号来指示线程已开始或结束执行,同时也提供了一些槽。

更有趣的是,QObject可以在多个线程中使用,发射信号以调用其他线程中的槽,并向“驻留”在其他线程中的对象发布事件。之所以能够实现这一点,是因为每个线程都可以拥有自己的事件循环。

QObject 的可重入性

QObject 是可重入的。其大多数非 GUI 子类,如QTimer 、QTcpSocket 、QUdpSocket 和QProcess ,也是可重入的,因此可以同时在多个线程中使用这些类。 请注意,这些类的设计初衷是仅在单个线程内创建和使用;在某个线程中创建对象,却在另一个线程中调用其函数,其正常运行无法得到保证。有三个限制需要注意:

  • QObject 的子对象必须始终在创建父对象的那个线程中创建。这意味着,除其他情况外,您绝不应将QThread 对象(this )作为在该线程中创建的对象的父对象(因为QThread 对象本身是在另一个线程中创建的)。
  • 事件驱动类只能在单个线程中使用。具体而言,这适用于定时器机制和network module 。例如,您不能在非object's thread 的线程中启动定时器或连接套接字。
  • 在删除 `QThread` 之前,必须确保该线程中创建的所有对象均已被删除。这可以通过在` run()` 的实现中将对象创建在栈上轻松实现。

尽管 `QObject ` 是可重入的,但 GUI 类(尤其是 `QWidget ` 及其所有子类)并非可重入的。它们只能在主线程中使用。如前所述,`QCoreApplication::exec()` 也必须在该线程中调用。

实际上,GUI 类无法在主线程以外的其他线程中使用的限制,可以通过将耗时操作放在单独的工作线程中,并在工作线程完成后由主线程将结果显示在屏幕上来轻松解决。这是实现“曼德布罗集”示例和“阻塞式 Fortune 客户端”示例所采用的方法。

一般而言,在QApplication 之前创建QObject是不被支持的,且根据平台的不同,可能会导致退出时发生奇怪的崩溃。这意味着QObject 的静态实例同样不被支持。结构合理的单线程或多线程应用程序应确保QApplication 是第一个被创建的,也是最后一个被销毁的QObject 。

按线程的事件循环

每个线程都可以拥有自己的事件循环。初始线程使用QCoreApplication::exec() 启动其事件循环,或者对于单对话框 GUI 应用程序,有时会使用QDialog::exec()。其他线程可以使用QThread::exec() 启动事件循环。与QCoreApplication 类似,QThread 提供了exit(int) 函数和quit() 插槽。

线程中的事件循环使该线程能够使用某些需要事件循环存在的非GUI Qt类(例如QTimer 、QTcpSocket 和QProcess )。它还允许将来自任何线程的信号连接到特定线程的槽上。下文“跨线程的信号与槽”一节对此进行了更详细的说明。

线程、对象和事件循环

QObject 实例被认为存在于其创建的线程中。该对象的事件由该线程的事件循环分发。可以通过QObject::thread() 获取QObject 所在的线程。

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 子类的函数,且该对象可能正在接收事件,则必须使用互斥锁(mutex)保护对QObject 子类内部数据的所有访问;否则,可能会导致程序崩溃或其他意外行为。

与其他对象一样,QThread 对象位于创建该对象的线程中——而不是位于调用QThread::run() 时创建的线程中。除非使用互斥锁保护成员变量,否则在QThread 子类中提供槽通常是不安全的。

另一方面,您可以安全地从QThread::run()的实现中发出信号,因为信号的发出是线程安全的。

跨线程的信号与槽

Qt 支持以下信号-槽连接类型:

  • Auto Connection (默认) 如果信号是在接收对象具有亲和性的线程中发出的,则行为与直接连接相同。否则,行为与队列连接相同。
  • Direct Connection 当信号被发出时,槽会立即被调用。槽在发出者的线程中执行,该线程不一定是接收者的线程。
  • Queued Connection 当控制权返回至接收方线程的事件循环时,槽会被调用。槽在接收方线程中执行。
  • Blocking Queued Connection 该槽函数的调用方式与“队列连接”相同,但当前线程会阻塞直至槽函数返回。

    注意: 在同一线程中使用 此类型连接对象将导致死锁。

  • Unique Connection 其行为与“自动连接”相同,但仅在不会重复现有连接的情况下才会建立连接。即,如果同一对对象的同一信号已连接到同一槽,则不会建立连接,且 `connect()` 返回 `false`。

可以通过向connect() 传递一个额外参数来指定连接类型。请注意,当发送方和接收方位于不同的线程中时,如果接收方的线程中正在运行事件循环,则使用直接连接是不安全的,原因与调用位于另一个线程中的对象的任何函数是不安全的一样。

QObject::connect() 本身是线程安全的。

曼德尔布罗特示例使用队列连接在工作线程和主线程之间进行通信。为了避免冻结主线程的事件循环(并因此冻结应用程序的用户界面),所有曼德尔布罗特分形计算都在一个单独的工作线程中进行。 当该线程完成分形渲染后,会发出一个信号。

同样,Blocking Fortune Client 示例也使用了一个单独的线程来与 TCP 服务器进行异步通信。

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.