线程和 QObjects

QThread 继承于 。它发出信号来指示线程开始或结束执行,并提供一些插槽。QObject

更有趣的是,QObjects 可以在多个线程中使用,发出信号以调用其他线程中的插槽,并向 "生活 "在其他线程中的对象发布事件。之所以能做到这一点,是因为每个线程都可以拥有自己的事件循环。

QObject 重入性

QObject 是可重入的。它的大多数非图形用户界面子类,如 、 、 和 ,也都是可重入的,因此可以同时在多个线程中使用这些类。请注意,这些类是为在单个线程中创建和使用而设计的;在一个线程中创建对象,然后在另一个线程中调用其函数,并不能保证有效。有三个限制需要注意:QTimer QTcpSocket QUdpSocket QProcess

  • QObject 的子代必须始终在创建父代的线程中创建。这意味着,除其他事项外,绝不能将QThread 对象 (this) 作为在线程中创建的对象的父对象传递(因为QThread 对象本身是在另一个线程中创建的)。
  • 事件驱动对象只能在单个线程中使用。 network module例如,不能在非object's thread 的线程中启动定时器或连接套接字。
  • 在删除QThread 之前,必须确保删除线程中创建的所有对象。在run() 实现中的堆栈上创建对象,就能轻松做到这一点。

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

实际上,在主线程以外的其他线程中无法使用图形用户界面类的问题很容易解决,方法是将耗时的操作放在单独的工作线程中,当工作线程结束后,在主线程的屏幕上显示结果。这就是实现Mandelbrot示例和Blocking Fortune Client 示例的方法。

一般来说,不支持在QApplication 之前创建 QObject,根据平台的不同,这可能会导致退出时出现奇怪的崩溃。这意味着也不支持QObject 的静态实例。结构合理的单线程或多线程应用程序应使QApplication 成为最先创建、最后销毁的QObject

每线程事件循环

每个线程都可以有自己的事件循环。初始线程使用QCoreApplication::exec() 启动事件循环,对于单对话框图形用户界面应用程序,有时使用QDialog::exec() 启动事件循环。其他线程可以使用QThread::exec() 启动事件循环。与QCoreApplication 一样,QThread 也提供了一个exit(int) 函数和一个quit() 槽。

线程中的事件循环使线程可以使用某些需要存在事件循环的非 GUI Qt 类(如QTimerQTcpSocketQProcess )。它还可以将任何线程的信号连接到特定线程的槽。下文 "跨线程的信号和槽"部分将对此进行详细说明。

线程、对象和事件循环

一个QObject 实例被认为生活在创建它的线程中。该对象的事件由该线程的事件循环调度。QObject 所在的线程可通过QObject::thread() 查看。

QObject::moveToThread() 函数可更改对象及其子对象的线程亲和性(如果对象有父对象,则无法移动)。

拥有对象的线程以外的线程调用deleteQObject )(或以其他方式访问对象)是不安全的,除非你能保证该对象当时没有在处理事件。如果使用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 支持这些信号槽连接类型:

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

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

  • Unique Connection 其行为与自动连接相同,但只有在不重复现有连接的情况下才会建立连接,也就是说,如果同一信号已连接到同一插槽的同一对对象,则不会建立连接,connect() 返回 。false

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

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

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

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

© 2025 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.