Qt 线程和 QObjects

线程和 QObjects

        QThread 继承于 QObject。 它发出信号来指示线程开始或结束执行,并提供一些插槽。
更有趣的是,QObjects 可以在多个线程中使用,发出信号以调用其他线程中的插槽,并向 "生活 "在其他线程中的对象发布事件。 之所以能做到这一点,是因为每个线程都可以拥有自己的事件循环。

QObject 重入性

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

  • QObject 的子对象必须始终在创建父对象的线程中创建。这意味着,除其他事项外,绝不能将 QThread 对象(this)作为在线程中创建的对象的父对象来传递(因为 QThread 对象本身是在另一个线程中创建的)。
  • //Qt 要求QObject的所有子对象都必须和其父对象在同一线程
    class CWorkObject: public QObject{}auto thread = new QThread(this);//线程将会this所在线程执行,不会开启新的线程
    QObject* work = new CWorkObject(thread); //错误,线程对象不能作为另一个线程,子对象work和父对象thread不在同一线程
    QObject* work2 = new CWorkObject(this);//存在父对象,无法移动到线程中
    work2->moveToThread(thread);//错误,work2有父对象this,不能移动到线程里
  • 事件驱动的对象只能在单个线程中使用。具体来说,这适用于定时器机制和网络模块。例如,不能在不是对象所在线程的线程中启动定时器或连接套接字。
  • 在删除 QThread 之前,必须确保删除线程中创建的所有对象。要做到这一点很容易,只要在run()过程中在堆栈上创建对象即可。

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

       实际上,在主线程之外的其他线程中不可能使用图形用户界面类,这一点很容易解决,方法是将耗时的操作放在单独的工作线程中,当工作线程结束后在主线程的屏幕上显示结果。 这就是实现Mandelbrot示例和Blocking Fortune Client 示例的方法。
        一般来说,不支持在 QApplication 之前创建 QObjects,根据平台的不同,这可能会导致退出时奇怪的崩溃。 这意味着也不支持 QObject 的静态实例。 结构合理的单线程或多线程应用程序应使 QApplication 成为第一个创建和最后一个销毁的 QObject。

每个线程的事件循环

每个线程都可以有自己的事件循环。初始线程使用QCoreApplication::exec() 启动事件循环,对于单对话框图形用户界面应用程序,有时使用QDialog::exec() 启动事件循环。其他线程可以使用QThread::exec() 启动事件循环。与QCoreApplication 一样,QThread 也提供了一个exit(int) 函数和一个quit() 槽。
线程中的事件循环使线程可以使用某些需要存在事件循环的非 GUI Qt 类(如QTimer 、QTcpSocket 和QProcess )。它还可以将任何线程的信号连接到特定线程的槽。下文 "跨线程的信号和槽"部分将对此进行详细说明。

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

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

拥有对象的线程以外的线程调用delete (QObject )(或以其他方式访问对象)是不安全的,除非你能保证该对象当时没有在处理事件。如果使用QObject::deleteLater() 代替,就会发布一个DeferredDelete 事件,对象线程的事件循环最终会接收到该事件。默认情况下,拥有 QObject 的线程是创建 QObject 的线程,但在调用QObject::moveToThread() 后就不是了。

如果没有事件循环在运行,事件就不会传递给对象。例如,如果在一个线程中创建了QTimer 对象,但从未调用过exec() ,那么QTimer 将永远不会发出timeout() 信号。调用deleteLater() 也不会起作用。(这些限制也适用于主线程)。

您可以使用线程安全函数QCoreApplication::postEvent() 随时向任何线程中的任何对象手动发布事件。事件将由创建对象的线程的事件循环自动派发。

所有线程都支持事件过滤器,但监控对象必须与被监控对象位于同一线程。同样,QCoreApplication::sendEvent() (与postEvent() 不同)只能用于向调用该函数的线程中的对象分派事件。

从其他线程访问 QObject 子类。

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 服务器进行异步通信。

Qt 模块中的线程支持

线程与 SQL 模块

数据库连接只能在创建它的线程中使用。可以使用QSqlDatabase::moveToThread() 将连接转移到另一个线程中。

此外,QSqlDrivers 使用的第三方库会对在多线程程序中使用 SQL 模块施加更多限制。更多信息请查阅数据库客户端手册。

在线程中绘制

QPainter 可在线程中用于在 QImage、QPrinter、QPicture 和(对于大多数平台)QPixmap 绘画设备上绘画。 不支持绘制到 QWidgets 上。 在 macOS 上,如果从 GUI 线程外打印,将不会显示自动进度对话框。
在任何给定时间内,任意数量的线程都可以进行绘制,但一次只能有一个线程在给定的绘制设备上进行绘制。 换句话说,如果两个线程分别在不同的 QImage 上绘制,它们可以同时绘制,但两个线程不能同时在同一个 QImage 上绘制。

线程和富文本处理

QTextDocument 、QTextCursor 以及所有相关类都是可重入的。

请注意,在图形用户界面线程中创建的QTextDocument 实例可能包含QPixmap 图像资源。请使用QTextDocument::clone() 创建文档副本,并将副本传递给另一个线程进行进一步处理(如打印)。

线程和 SVG 模块

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

线程和隐式共享类

Qt 对其许多值类(尤其是QImage 和QString )使用了一种称为隐式共享的优化方法。从 Qt 4 开始,隐式共享类可以像其他值类一样安全地跨线程复制。它们是完全可重入的。隐式共享确实是隐式的

在很多人的印象中,隐式共享和多线程是不相容的概念,因为引用计数通常是这样进行的。然而,Qt 使用原子引用计数来确保共享数据的完整性,避免了引用计数器的潜在损坏。

请注意,原子引用计数并不能保证线程安全。在线程间共享隐式共享类的实例时,应使用适当的锁定。这是对所有重入类的相同要求,无论是否共享。不过,原子引用计数确实能保证在隐式共享类的本地实例上工作的线程是安全的。我们建议使用信号和槽在线程间传递数据,因为这样做无需任何显式锁定。

总而言之,Qt 4 中的隐式共享类确实是隐式共享。即使在多线程应用程序中,您也可以像使用普通的、非共享的、基于值的可重入类一样安全地使用它们。

Threads and QObjects | Qt 6.8

Thread-Support in Qt Modules | Qt 6.8

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/73771.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为、浪潮、华三链路聚合概述

1、华为 链路聚合可以提高链路带宽和链路冗余性。有三种类型,分别是手工链路聚合,静态lacp链路聚合,动态lacp链路聚合。 手工链路模式:也称负载分担模式,需手动指定链路,各链路之间平均分担流量。静态LAC…

HarmonyOS NEXT 鸿蒙中关系型数据库@ohos.data.relationalStore API 9+

核心API ohos.data.relationalStore API 9 数据库 数据库是存储和管理数据的系统 数据库(Database)是一个以特定方式组织、存储和管理数据的集合,通常用于支持各种应用程序和系统的运行。它不仅是存放数据的仓库,还通过一定的…

步进电机 cia402协议 报文自己的理解 (笔记)

1. cai402 协议是什么 CiA 402 协议(CAN in Automation 402),它是工业自动化领域中的一种通信协议,主要用于运动控制(如伺服驱动器、步进电机等)( )所属标准 CiA 402 是 CANopen 应用…

鸿蒙摄像机,一场智能安防的“平权革命”

2025的春天,全国各行各业都感受到了普惠AI的魅力。大模型带来的技术平权,让每一个人都能轻松用上AI。 这时候,企业想知道,每时每刻离不开的摄像机,究竟什么时候才能迎来智能技术的平权与普惠。 博思数据研究中心的一份…

解决HuggingFaceEmbeddings模型加载报错:缺少sentence-transformers依赖包

遇到报错 报错信息: Error loading model: Could not import sentence_transformers python package. Please install it with pip install sentence-transformers. 装包信息: pip install modelscope langchain sentence_transformers langchain-huggingface on…

从泛读到精读:合合信息文档解析如何让大模型更懂复杂文档

从泛读到精读:合合信息文档解析如何让大模型更懂复杂文档 一、引言:破解文档“理解力”瓶颈二、核心功能:合合信息的“破局”亮点功能亮点1:复杂图表的高精度解析图表解析:为大模型装上精准“标尺”表格数据精准还原 功…

Python+requests实现接口自动化测试框架

为什么要做接口自动化框架 1、业务与配置的分离 2、数据与程序的分离;数据的变更不影响程序 3、有日志功能,实现无人值守 4、自动发送测试报告 5、不懂编程的测试人员也可以进行测试 正常接口测试的流程是什么? 确定接口测试使用的工具…

信息学奥赛一本通 1514:【例 2】最大半连通子图 | 洛谷 P2272 [ZJOI2007] 最大半连通子图

【题目链接】 ybt 1514:【例 2】最大半连通子图 洛谷 P2272 [ZJOI2007] 最大半连通子图 【题目考点】 1. 图论:强连通分量 缩点 2. 图论:拓扑排序 有向无环图动规 【解题思路】 对于图中任意两顶点u、v,满足u到v或v到u有路径…

Android打aar包问题总结

1、moduleA 依赖 moduleB,将moduleA打包成aar时,未包含 moduleB的resources资源; 方法一:将moduleB的资源,手动拷贝一份到moduleA中; 方法二:使用 fat-aar 插件; 2、fat-aar插件使…

【网络协议】【http】http 简单介绍

【网络协议】【http】http 简单介绍 1 HTTP 头部 HTTP 是一种请求-响应协议,客户端向服务器发送请求,服务器返回响应。 1.1 HTTP 状态码 状态码是服务器返回给客户端的 三位数字代码,用于表示请求的执行结果。 状态码按照首位数字分类&am…

谈谈空间复杂度考量,特别是递归调用栈空间消耗?

空间复杂度考量是算法设计的核心要素之一,递归调用栈的消耗问题在前端领域尤为突出。 以下结合真实开发场景进行深度解析: 一、递归调用栈的典型问题 1. 深层次DOM遍历的陷阱 // 危险操作:递归遍历未知层级的DOM树 function countDOMNode…

LeetCode算法题(Go语言实现)_16

题目 给定一个二进制数组 nums 和一个整数 k&#xff0c;假设最多可以翻转 k 个 0 &#xff0c;则返回执行操作后 数组中连续 1 的最大个数 。 一、代码实现 func longestOnes(nums []int, k int) int {left, zeroCnt, maxLen : 0, 0, 0for right : 0; right < len(nums); …

【数据结构】栈 与【LeetCode】20.有效的括号详解

目录 一、栈1、栈的概念及结构2、栈的实现3、初始化栈和销毁栈4、打印栈的数据5、入栈操作---栈顶6、出栈---栈顶6.1栈是否为空6.2出栈---栈顶 7、取栈顶元素8、获取栈中有效的元素个数 二、栈的相关练习1、练习2、AC代码 个人主页&#xff0c;点这里~ 数据结构专栏&#xff0c…

攻破tensorflow,勇创最佳agent(2)---损失(loss) 准确率(accuracy)问题

实战播: 怎么判定一个模型好不好,你设置的值对不对? 需要再看几个值: 例如: model Sequential()for units in model_structure:model.add(Dense(units, activationrelu))model.add(Dropout(train_config.get(dropout_rate, 0.3)))model.add(Dense(1, activationsigmoid)) 他…

pdfh5 pdf

踩坑1&#xff1a; 渲染失败 &#xff08;1&#xff09;在vue项目中&#xff0c;读取本地的pdf文件需要放到public下static文件夹中&#xff0c;不能放在别的地方&#xff1b; &#xff08;2&#xff09;引用时&#xff0c;不能使用相对路径&#xff0c;因为使用public文件下…

6.5 模拟专题:LeetCode 38. 外观数列

1. 题目链接 LeetCode 38. 外观数列 2. 题目描述 给定一个正整数 n&#xff0c;生成外观数列的第 n 项。外观数列的定义如下&#xff1a; 第 1 项为 "1"。第 n 项是对第 n-1 项的描述。例如&#xff0c;第 2 项描述第 1 项&#xff08;"1"&#xff09;为…

什么是具身智能

具身智能&#xff08;Embodied Intelligence&#xff09;是人工智能与机器人学交叉的前沿领域&#xff0c;强调智能体通过身体与环境的动态交互实现自主学习和进化&#xff0c;其核心在于将感知、行动与认知深度融合‌。通俗地讲&#xff0c;就是机器人或者智能系统在物理环境中…

git命令使用小记(打补丁)

需求&#xff1a;需要从开发分支提取本人提交代码&#xff0c;然后合并到主分支 一、制作补丁包 mkdir -p patches for commit in $(git log commitA..commitB --author"username" --reverse --prettyformat:"%h"); do …

mapbox基础,加载popup弹出窗

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️mapboxgl.Map style属性1.3 ☘️popup 弹出窗 api1.3.1 ☘️构造函数1.…

C++11--(1)

目录 1.列表初始化 {}初始化 C98中 C11中 内置置类型和自定义类型 创建对象也适用 std::initializer_list 2.变量类型推导 auto C98 C11 decltype nullptr 3.范围for循环 4.STL中一些变化 array 1.创建和初始化 2.访问元素 ​编辑 3.修改操作 4.支持迭代器…