线程和 QObject

QThread 继承 QObject 。它发射指示线程启动 (或执行完成) 的信号,且还提供了几个槽。

更有趣的是 QObject 可以用于多线程,发射援引其它线程槽的信号,并把事件张贴给 "存活" 于其它线程中的对象。这是可能的,因为每个线程都允许拥有它自己的事件循环。

QObject 重入

QObject 可重入。它的大多数非 GUI (图形用户界面) 子类,譬如 QTimer , QTcpSocket , QUdpSocket , QFtp ,和 QProcess ,也可重入,使之可能从多线程同时使用这些类。注意,设计是从单线程创建和使用这些类;在某个线程中创建对象并从另一线程调用其函数,不能保证能工作。要注意存在 3 个约束:

  • 子级对于 QObject 必须始终在创建父级的线程中创建。 这隐含,除其它事情外,从不应该传递 QThread 对象 ( this ) 作为在线程中创建对象的父级 (由于 QThread 对象自身是在另一线程中创建的)。
  • 事件驱动对象只可以用于单线程。 具体来说,这适用于 计时器机制 网络模块 。例如,无法启动计时器 (或连接套接字) 当所在的线程不是 对象的线程 .
  • 必须确保删除线程中创建的所有对象,先于删除 QThread . 可以轻松做到这,通过创建堆栈对象在 run() 实现。

尽管 QObject 是可重入 GUI 类,显而易见 QWidget 及其所有子类,都不可重入。只可以从主线程使用它们。如前所述, QCoreApplication::exec () 还必须从该线程调用。

在实践中,不可能在主线程外的其它线程中使用 GUI (图形用户界面) 类,通过把耗时操作放入单独工作者线程,并在工作线程完成时在主线程屏幕中显示结果,可轻松解决。此实现方式可以用于 Mandelbrot 阻塞 Fortune 客户端 范例。

每线程事件循环

Each thread can have its own event loop. The initial thread starts its event loops using QCoreApplication::exec (); other threads can start an event loop using QThread::exec ()。像 QCoreApplication , QThread 提供 exit (int) 函数和 quit() 槽。

线程中的事件循环使之可能对要使用某些非 GUI Qt 类的线程,要求存在事件循环 (譬如 QTimer , QTcpSocket ,和 QProcess )。还使之可能把来自任何线程的信号,连接到特定线程槽。阐述这的更多细节在 信号和槽跨线程 以下章节。

Threads, objects, and event loops

A QObject 实例据称是 live 在创建它的线程中。此对象的事件由该线程的事件循环分派。线程对于 QObject 存活的获得是使用 QObject::thread ().

Note that for QObjects that are created before QApplication , QObject::thread () returns zero. This means that the main thread will only handle posted events for these objects; other event processing is not done at all for objects with no thread. Use the QObject::moveToThread () function to change the thread affinity for an object and its children (the object cannot be moved if it has a parent).

调用 delete QObject 从线程而不是某个线程其 owns 对象 (或以其它方式访问对象) 是不安全的,除非保证对象在那刻不处理事件。使用 QObject::deleteLater () 代替,和 DeferredDelete 事件将被张贴,最终将拾取对象线程的事件循环。默认情况下,线程 owns a QObject 是线程 creates the QObject ,但不后于 QObject::moveToThread () 被调用。

若没有事件循环在运行,就不会将事件交付给对象。例如,若创建 QTimer 对象在线程,但从不调用 exec() QTimer 将从不发射其 timeout() 信号。调用 deleteLater() 也不会工作 (这些限定也适用于主线程)。

可以在任何时间手动将事件张贴给任何线程中的任何对象,使用线程安全函数 QCoreApplication::postEvent ()。将通过创建对象线程的事件循环,自动分派事件。

支持事件过滤器的所有线程,具有监视对象必须活在如被监视对象的同一线程中的限定。同样, QCoreApplication::sendEvent () (不像 postEvent() ) 只可以用于把事件分派给活在从那里调用函数的线程的对象。

从其它线程访问 QObject 子类

QObject 及其所有子类都不是线程安全的。这包括整个事件交付系统。它很重要,记住事件循环可能把事件交付给 QObject 子类,当从另一线程访问对象时。

若正调用函数在 QObject 子类未活在当前线程中且对象可能接收事件,就必须保护所有访问对 QObject 子类的内部数据按互斥;否则,可能经历崩溃 (或其它不期望行为)。

像其它对象, QThread 对象活在创建对象的线程中 -- not 在创建线程中,当 QThread::run () 被调用。提供槽通常不安全在 QThread 子类,除非采用互斥保护成员变量。

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

信号和槽跨线程

Qt 支持这些信号/槽连接类型:

  • 自动连接 (默认) 若在拥有亲缘关系的接收对象的线程中发射信号,那么行为如同直接连接。否则,行为如同队列连接。
  • 直接连接 立即援引槽,当发射信号时。在发射器线程中执行槽,不必是接收者线程。
  • 队列连接 槽被援引,当控制返回给接收者线程的事件循环时。在接收者线程中执行槽。
  • 阻塞队列连接 如同队列连接援引槽,除当前线程阻塞,直到槽返回外。

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

  • 唯一连接 The behavior is the same as the Auto Connection, but the connection is made only if it does not duplicate an existing connection. i.e., if the same signal is already connected to the same slot for the same pair of objects, then the connection is not made and connect() returns false.

可以指定连接类型,通过把额外自变量传递给 connect() 。要意识到,当发送者和接收者活在不同线程中时 (若事件循环运行在接收者线程中) 使用直接连接不安全,出于相同原因,调用活在另一线程中对象的任何函数也不安全。

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

The Mandelbrot example uses a queued connection to communicate between a worker thread and the main thread. To avoid freezing the main thread's event loop (and, as a consequence, the application's user interface), all the Mandelbrot fractal computation is done in a separate worker thread. The thread emits a signal when it is done rendering the fractal.

同样, 阻塞 Fortune 客户端 example uses a separate thread for communicating with a TCP server asynchronously.