【Threads and QObjects,Thread-Support in Qt Modules】线程和QObjects,Qt模块中的线程支持

Threads and QObjects

Threads 继承了 QObjects类。它发出信号来指示线程已开始执行或已完成执行,并提供一些槽函数。
更有趣的是,QObjects可以在多个线程中使用,发出调用其他线程中槽的信号,并将事件发布到“live”在其他线程中的对象。这是可能的,因为每个线程都允许有自己的事件循环。

QObject Reentrancy QObject的重入

QObject是可重入的。它的大多数非GUI子类,如QTimer、QTcpSocket、QUdpSocket和QProcess,也是可重入的,因此可以同时从多个线程使用这些类。请注意,这些类被设计为在单个线程中创建和使用;不能保证在一个线程中创建对象并从另一个线程调用其函数。有三个限制需要注意:
1、QObject的子对象必须始终在创建父对象的线程中创建。这意味着,除其他外,永远不应该将QThread对象(This)作为在线程中创建的对象的父对象传递(因为QThread物体本身是在另一个线程中创建)。
2、事件驱动的对象只能在单个线程中使用。具体地,这适用于定时器机制和网络模块。例如,不能在不是对象线程的线程中启动计时器或连接套接字。
3、删除QThread之前,必须确保删除在线程中创建的所有对象。这可以通过在run()实现中的堆栈上创建对象来轻松完成。
尽管QObject是可重入的,但GUI类,尤其是QWidget及其所有子类,是不可重入。它们只能从主线程使用。如前所述,还必须从该线程调用QCoreApplication::exec()。在实践中,通过将耗时的操作放在单独的工作线程中,并在工作线程完成时在主线程的屏幕上显示结果,可以很容易地解决在主线程以外的其他线程中使用GUI类的不可能性。这是用于实现Mandelbrot示例Blocking Fortune客户端示例的方法。
通常,不支持在QApplication之前创建QObjects,这可能会导致退出时出现奇怪的崩溃,具体取决于平台。这意味着也不支持QObject的静态实例。一个结构合理的单线程或多线程应用程序应该使QApplication成为第一个创建的和最后一个销毁的QObject。

Per-Thread Event Loop 每线程事件循环

每个线程都可以有自己的事件循环。初始线程使用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的线程是创建QOObject的线程,但不是在调用QObject::moveToThread()之后。
如果没有事件循环正在运行,则不会将事件传递到对象。例如,如果在线程中创建一个QTimer对象,但从不调用exec(),则QTimer将永远不会发出其timeout()信号。调用deleteLater()也不起作用。(这些限制也适用于主线程。)
您可以使用线程安全函数QCoreApplication::postEvent()随时手动将事件发布到任何线程中的任何对象。事件将由创建对象的线程的事件循环自动调度。
所有线程都支持事件筛选器,但有一个限制,即监视对象必须与被监视对象位于同一线程中。类似地,QCoreApplication::sendEvent()(与postEvent()不同)只能用于将事件调度到调用函数的线程中的对象。

Accessing QObject Subclasses from Other Threads从其他线程访问QObject子类

QObject及其所有子类都不是线程安全的。这包括整个事件传递系统。重要的是要记住,当您从另一个线程访问对象时,事件循环可能会将事件传递到您的QObject子类。
如果您正在调用一个不在当前线程中的QObject子类上的函数,并且该对象可能会接收事件,则必须使用互斥锁保护对QObject个子类内部数据的所有访问;否则,您可能会遇到崩溃或其他不希望的行为。
与其他对象一样,QThread对象位于创建对象的线程中,而不是在调用QThread::run()时创建的线程中。在QThread子类中提供槽通常是不安全的,除非使用互斥体保护成员变量。
另一方面,您可以安全地从QThread::run()实现中发出信号,因为信号发射是线程安全的。

Signals and Slots Across Threads跨线程的信号和插槽

Qt支持以下信号槽连接类型:
自动连接(默认)如果信号是在接收对象具有关联性的线程中发出的,则行为与直接连接相同。否则,行为与排队连接相同。
直接连接发出信号时,会立即调用槽。槽在发射器的线程中执行,而发射器的线程不一定是接收器的线程。
排队连接当控制返回到接收器线程的事件循环时,将调用槽。槽在接收器的线程中执行。
阻塞排队连接调用槽时与调用排队连接时一样,但当前线程会阻塞,直到槽返回为止。注意:使用此类型连接同一线程中的对象将导致死锁。
唯一连接此行为与自动连接相同,但只有在不复制现有连接的情况下才能建立连接。即,如果同一信号已经连接到同一对对象的同一插槽,则不进行连接,connect()返回false。

可以通过向connect()传递额外的参数来指定连接类型。请注意,如果事件循环在接收方的线程中运行,则在发送方和接收方位于不同线程中时使用直接连接是不安全的,原因与调用位于另一个线程中的对象上的任何函数是不安全。
QObject::connect()本身是线程安全的。
Mandelbrot示例使用排队连接在工作线程和主线程之间进行通信。为了避免冻结主线程的事件循环(以及应用程序的用户界面),所有Mandelbrot分形计算都在一个单独的工作线程中完成。渲染完分形后,线程会发出一个信号。
类似地,Blocking Fortune客户端示例使用单独的线程与TCP服务器异步通信。

Mandelbrot示例和Blocking Fortune客户端示例 查看这篇博客。

Thread-Support in Qt Modules Qt模块中的线程支持

线程和SQL模块

连接只能在创建它的线程内使用。不支持在线程之间移动连接或从其他线程创建查询。
此外,QSqlDrivers使用的第三方库可能会对在多线程程序中使用SQL模块施加进一步的限制。

在线程绘画

QPainter可以在线程中用于在QImage、QPrinter和QPicture绘画设备上绘画。不支持在QPixmap和QWidget上绘制。在macOS上,如果从GUI线程外部打印,则不会显示自动进度对话框。
任何数量的线程都可以在任何给定的时间进行绘制,但是一次只能在给定的绘制设备上绘制一个线程。换言之,如果两条线程分别绘制在不同的QImage上,则两条线程可以同时绘制,但这两条线程不能同时绘制在同一QImage上。

线程与富文本处理

QTextDocument、QTextCursor和所有相关的类都是可重入的。
请注意,在GUI线程中创建的QTextDocument实例可能包含QPixmap图像资源。使用QTextDocument::clone()创建文档的副本,并将副本传递给另一个线程进行进一步处理(如打印)。

线程和SVG模块

QtSvg模块中的QSvgGenerator和QSvgRenderer类是可重入的。

线程和隐含共享类

Qt对其许多值类使用了一种称为隐式共享的优化,尤其是QImage和QString。从Qt4开始,隐式共享类可以像任何其他值类一样安全地跨线程复制。它们是完全可重入的。隐性共享实际上是隐性的。
在许多人看来,由于引用计数的典型方式,隐式共享和多线程是不兼容的概念。然而,Qt使用原子引用计数来确保共享数据的完整性,避免引用计数器的潜在损坏。
请注意,原子引用计数不能保证线程安全。在线程之间共享隐式共享类的实例时,应使用适当的锁定。这是对所有可重入类的相同要求,无论是否共享。然而,原子引用计数确实保证了在隐式共享类的本地实例上工作的线程是安全的。我们建议使用信号和插槽在线程之间传递数据,因为这可以在不需要任何显式锁定的情况下完成。
综上所述,Qt4中的隐式共享类实际上是隐式共享的。即使在多线程应用程序中,也可以安全地使用它们,就好像它们是普通的、非共享的、可重入的基于值的类一样。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值