多线程Qt下的八条规则(qt多线程直接处理数据)
ztj100 2025-04-08 20:52 21 浏览 0 评论
相信资深Qter都认识Giuseppe D’Angelo,这位有着二十多年Qt开发经验,Qt源码行数贡献的最多的开发者之一,同时也是Qt项目的审批者,所说话的份量不言而喻。
原文作者: 朱塞普 D 安吉洛(Giuseppe D’Angelo)
多线程一直是一个老生常谈的话题,而Qt作为C++生态非常重要的一环,熟练使用多线程是必备的技能。
Qt中封装了很多多线程的轮子,我们在日常开发中,除了要学会怎么用之外,如何用好才是关键。
下面来看一下可以代表Qt官方的Giuseppe D’Angelo是如何告诫大家的。
尽管多线程的概念看起来非常简单,但在日常编码中,使用多线程总会遇到难以复现和追踪的恶心BUG。这导致我们使用多线程写出可靠代码是非常困难的。接下来让我们深入了解一下,为什么会出现这种状况。
首先你需要对框架底层、编程语言、编译器的有深层次的理解,这样你才能知道如何避免线程高发问题;其次,你还要理解同步原语,使用合适的设计模式来编写多线程代码,只有这样才能使多业务在所有条件下正常执行;最后,你还要学会使用调试工具来调试多线程,只有这样你才能发现多线程内部难以复现的BUG。
当涉及到Qt多线程后,尤其需要了解你的框架和设计模式。Qt能让你写出令人惊奇的多线程程序,也能让你开枪打自己的脚。多年的Qt框架、Qt客户端多线程BUG查找和修复经验磨练了我们多线程编码水平,这里有几条我们总结出来的顶级信条,它可以让你避免多线程常见问题,可以让你的程序在第一时间正常运行。
1. 避免使用QThread::sleep()
尽管 QThread::sleep() 是一个让你的线程休眠的API,如果你使用了它,那么你应该重新学习一下事件驱动设计。如果你将“多线程休眠”改为“多线程事件等待”(或者最好是不使用多线程),那么你将会节省巨大的系统资源,不然这些资源就被空转的线程浪费了。QThread::sleep() 用作计时器,也是一个糟糕的设计,因为从线程休眠到返回控制的这段时间,是很难受到控制的。理论上讲,当进程终止时,休眠的线程也会导致问题。前台线程可以阻止程序终止,直到他们被唤醒,而后台线程永远不会唤醒前台线程,结果就是程序的清理终止一直被阻止。
2. 禁止子线程操作GUI
Qt 的 GUI 操作不是线程安全的,所以非主线程外一切线程对GUI的操作都是不安全的。这就意味着窗口小部件、QtQuick、QPixmap,以及其他任何类的对象都不能在非主线程操作GUI。当然也有些特例:GUI函数只操作数据,不触及窗口UI管理,这是可以被子线程调用的,比如 QImage 和 QPainter。但是依旧要小心,像 QBitmap、 QPixmap 这些类依旧是线程不安全的。可以查询每个 API 的官方文档:如果你没有看到 可重入(reentrant) 标识,那么在子线程的一切调用都是不安全的。
3. 不要阻塞主线程
不要在主线程调用任何不明确阻塞时间的函数(比如 QThread::wait() ),因为这些函数会使你主线程的事件循环暂停,造成UI冻结。如果你的主线程阻塞时间足够长,那么操作系统会认为你的程序冻结,会询问你要不要干掉它(鲲哥: Windows常见于 “窗口未响应” )。不管是无限阻塞还是长时间阻塞主线程,对程序来说都是很搓的做法。
4. 一定要在拥有QObjects所有权的线程上对其进行销毁
Qt从设计上就不允许在非拥有QObject 所有权的线程上对其进行销毁,这意味这,一个QThread在销毁之前,它所掌握的所有QObject必须先销毁。如果没有这么做,那么很可能引发数据完整问题,比如常见的内存泄露和进程 crash 。
那么如何确定销毁QObject 的线程就是拥有其所有权的线程呢?我们可以将其创建为在QThread::run() 方法函数内执行的自动变量,关联QThread::finished() 信号与QThread::deleteLater() 槽函数;也可以通过moveToThread() 将QObject 移动到其他线程进行延迟析构。注意,一旦你把QObject从原来拥有它的线程移动到新线程后,你就不能用原来的线程操作它了,因为它的所有权属于新的线程了。
5. 线程同步的时候,不要相信自己的直觉
一种非常常见的设计方式就是一个线程把自己的运行状态,通过布尔变量的形式传递给监控线程,供其监控。一个简单的数据结构,一个线程写,另一个线程读,这样看起来是不需要做同步保护,因为我们能确保最终能读到它,是这样吗?直觉上看起来是没问题的,实际上就是这么简单一个案例也是线程不安全的。
C++标准规定线程同步是强制性的,任何超纲的行为都会导致未定义行为(UB)。如果你并没有做线程同步,即使是一个简单案例,也会给你制造问题。事实上,Linux内核发现的一些严重BUG就是这么产生的。我们最应该做的不是过度思考安全与否,假设有一个被多个线程同时操作的数据,即使这个数据看起来多么人畜无害,产生问题的几率有多么小,我们一定要用合适同步手段来保护这个数据。
6. 假设QObject是不可重入的
一个函数能被不同的线程不同数据同时访问,且不需要任何同步方法保护,那么这个函数就是可重入的。Qt官方文档显示QObject是可重入的,但实际上却有很多警告:
- 基于事件的类型是不可重入的(定时器、socket等等)
- 假设某一QObject正在其归属的线程中进行事件派发,如果你在另一个线程操作它,这就可能会导致竞争。
- 从属于同一父子关系树的所有QObject对象,必须在同一个线程进行处理。
- 删除QThread 对象之前,必须先把其拥有的所有QObject对象干掉。
- 你必须在一个对象归属的线程里面使用对这个对象使用 moveToThread() 。
为了避免以上所有特殊场景,最简单的方法就是认为QObject 是不可重入的。事实上,这就意味着你必须在QObject 所属的线程上操作它。这也会让你远离所有可能会出现问题,同时也是难以察觉的场景。
7. 避免往QThread增加槽函数
因为QThread 对象和创建他们的线程存在关联(或许不存在关联),但在子线程使用信号槽的时候,很容易产生问题。尽管子线程可以使用槽函数,当需要规避大量难以察觉的缺陷时,我们建议你不要这么做。
如果你不需要重写QThread::run() 函数,那么请不要从QThread 派生子类。只需要创建一个QThread 对象即可,这样你就能避免槽函数问题(更多请关注作者博客的其他博文)。
8. 请使用C++标准库,因为更好用
最后,不管是C++标准库,还是第三方库,在多线程方面都比Qt更具特色。它们包含Qt没有的功能——并行算法、协程、锁存器、内存屏障、原子智能指针、任务延续、executors、并发队列、分布式计数器等等。
Qt的多线程在一些场景下仍然好用,比如C++标准库里面没有线程池,但是Qt就有。好消息就是C++标准库和Qt完美兼容,你可以自由地将标准库引入你的代码里面。事实上,除非你要用线程操作QObject 对象才必须用到QThread,不然使用C++标准库还是Qt的多线程库,依赖于你个人喜好。
相关推荐
- 作为后端开发,你知道MyBatis有哪些隐藏的 “宝藏” 扩展点吗?
-
在互联网大厂后端开发领域,MyBatis作为一款主流的持久层框架,凭借其灵活的配置与强大的数据处理能力,广泛应用于各类项目之中。然而,随着业务场景日趋复杂、系统规模不断扩张,开发过程中常面临SQL...
- 基于Spring+SpringMVC+Mybatis分布式敏捷开发系统架构(附源码)
-
前言zheng项目不仅仅是一个开发架构,而是努力打造一套从前端模板-基础框架-分布式架构-开源项目-持续集成-自动化部署-系统监测-无缝升级的全方位J2EE企业级开发解...
- 基于Java实现,支持在线发布API接口读取数据库,有哪些工具?
-
基于java实现,不需要编辑就能发布api接口的,有哪些工具、平台?还能一键发布、快速授权和开放提供给第三方请求调用接口的解决方案。架构方案设计:以下是一些基于Java实现的无需编辑或只需少量编辑...
- Mybatis Plus框架学习指南-第三节内容
-
自动填充字段基本概念MyBatis-Plus提供了一个便捷的自动填充功能,用于在插入或更新数据时自动填充某些字段,如创建时间、更新时间等。原理...
- 被你误删了的代码,在 IntelliJ IDEA中怎么被恢复
-
在IntelliJIDEA中一不小心将你本地代码给覆盖了,这个时候,你ctrl+z无效的时候,是不是有点小激动?我今天在用插件mybatisgenerator自动生成mapper的时候,...
- 修改 mybatis-generator 中数据库类型和 Java 类型的映射关系
-
使用mybatis-generator发现数据库类型是tinyint(4),生成model时字段类型是Byte,使用的时候有点不便数据库的类型和Model中Java类型的关系...
- 又被问到了, java 面试题:反射的实现原理及用途?
-
一、反射的实现原理反射(Reflection)是Java在运行时动态获取类的元数据(如方法、字段、构造器等)并操作类对象的能力。其核心依赖于...
- Spring Boot 中JPA和MyBatis技术那个更好?
-
你在进行SpringBoot项目开发时,是不是也经常在选择JPA和MyBatis这两个持久化技术上犯难?面对众多前辈的经验之谈,却始终拿不准哪种技术才最适合自己的项目?别担心,今天咱们就...
- Spring Boot (七)MyBatis代码自动生成和辅助插件
-
一、简介1.1MyBatisGenerator介绍MyBatisGenerator是MyBatis官方出品的一款,用来自动生成MyBatis的mapper、dao、entity的框架,让...
- 解决MyBatis Generator自动生成.java.1文件
-
MyBatis框架操作数据库,一张表对应着一个实体类、一个Mapper接口文件、一个Mapper映射文件。一个工程项目通常最少也要几十张表,那工作量可想而知非常巨大的,MyBatis框架替我们想好了解...
- Linux yq 命令使用详解
-
简介yq是一个轻量级、可移植的命令行...
- Python学不会来打我(62) json数据操作汇总
-
很多小伙伴学了很久的python一直还是没有把数据类型之间的转换搞明白,上一篇文章我们详细分享了python的列表、元组、字典、集合之间的相互转换,这一篇文章我们来分享json数据相关的操作,虽然严格...
- 之前3W买的Python全系列教程完整版(懂中文就能学会)
-
今天给大家带来了干货,Python入门教程完整版,完整版啊!完整版!言归正传,小编该给大家介绍一下这套教程了,希望每个小伙伴都沉迷学习,无法自拔...
- x-cmd pkg | grex - 正则表达式生成利器,解决手动编写的烦恼
-
简介grex是一个旨在简化创作正则表达式的复杂且繁琐任务的库和命令行程序。这个项目最初是DevonGovett编写的JavaScript工具regexgen的Rust移植。但re...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 作为后端开发,你知道MyBatis有哪些隐藏的 “宝藏” 扩展点吗?
- 基于Spring+SpringMVC+Mybatis分布式敏捷开发系统架构(附源码)
- 基于Java实现,支持在线发布API接口读取数据库,有哪些工具?
- Mybatis Plus框架学习指南-第三节内容
- 被你误删了的代码,在 IntelliJ IDEA中怎么被恢复
- 修改 mybatis-generator 中数据库类型和 Java 类型的映射关系
- 又被问到了, java 面试题:反射的实现原理及用途?
- Spring Boot 中JPA和MyBatis技术那个更好?
- Spring Boot (七)MyBatis代码自动生成和辅助插件
- 解决MyBatis Generator自动生成.java.1文件
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)