【QT】线程和QObject

线程和QObject

QThread 继承自 QObject。它会发射信号来指示线程开始或结束执行,并且也提供了一些槽函数。

更有趣的是,QObject 可以在多个线程中使用,发射信号以调用其他线程中的槽函数,并向在其他线程中"活动"的对象发送事件。这是因为每个线程都可以拥有自己的事件循环。

QObject 的可重入性

QObject 是可重入的。它的大多数非 GUI 子类,如 QTimer、QTcpSocket、QUdpSocket 和 QProcess,也是可重入的,这使得可以同时从多个线程中使用这些类。需要注意的是,这些类设计为在单个线程中创建和使用;在一个线程中创建对象,然后从另一个线程中调用其函数并不保证可行。有三个约束需要注意:

  • 一个 QObject 的子对象必须始终在其父对象的线程中创建。 这意味着,除其他事项外,您不应将 QThread 对象(this)作为在该线程中创建的对象的父对象(因为 QThread 对象本身是在另一个线程中创建的)。
  • 仅允许在单个线程中使用事件驱动的对象。 具体来说,这适用于定时器机制和网络模块。例如,您不能在不是对象的线程中启动定时器或连接套接字。
  • 必须确保在线程中创建的所有对象在删除 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() 函数可以更改对象及其子对象的线程关联性(如果对象有父对象,则无法移动该对象)。

在不是 拥有 该对象的线程上调用 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 支持以下这些信号-槽连接类型:

  • 自动连接(默认):如果信号是在接收对象具有关联性的线程中发射的,则行为与直接连接相同。否则,行为与排队连接相同。

  • 直接连接:当信号被发射时,槽会立即被调用。槽在发射器的线程中执行,这不一定是接收器的线程。

  • 排队连接:当控制返回到接收器线程的事件循环时,槽会被调用。槽在接收器的线程中执行。

  • 阻塞排队连接:槽会像排队连接一样被调用,除了当前线程会阻塞直到槽返回。

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

  • 唯一连接:行为与自动连接相同,但只有在它不重复现有连接时才会建立连接。即,如果相同信号已经连接到相同对象的相同槽,那么连接不会建立,connect() 返回 false

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

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

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

件循环(以及因此冻结应用程序的用户界面),所有 Mandelbrot 分形计算都在单独的工作线程中完成。当线程完成渲染分形时,会发射一个信号。

类似地,阻塞式 Fortune 客户端示例 使用一个单独的线程来异步与 TCP 服务器通信。

  • 10
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值