百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

多线程Qt下的八条规则(qt多线程直接处理数据)

ztj100 2025-04-08 20:52 10 浏览 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

QtGUI 操作不是线程安全的,所以非主线程外一切线程对GUI的操作都是不安全的。这就意味着窗口小部件、QtQuickQPixmap,以及其他任何类的对象都不能在非主线程操作GUI。当然也有些特例:GUI函数只操作数据,不触及窗口UI管理,这是可以被子线程调用的,比如 QImageQPainter。但是依旧要小心,像 QBitmapQPixmap 这些类依旧是线程不安全的。可以查询每个 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的多线程库,依赖于你个人喜好。

相关推荐

Vue 技术栈(全家桶)(vue technology)

Vue技术栈(全家桶)尚硅谷前端研究院第1章:Vue核心Vue简介官网英文官网:https://vuejs.org/中文官网:https://cn.vuejs.org/...

vue 基础- nextTick 的使用场景(vue的nexttick这个方法有什么用)

前言《vue基础》系列是再次回炉vue记的笔记,除了官网那部分知识点外,还会加入自己的一些理解。(里面会有部分和官网相同的文案,有经验的同学择感兴趣的阅读)在开发时,是不是遇到过这样的场景,响应...

vue3 组件初始化流程(vue组件初始化顺序)

学习完成响应式系统后,咋们来看看vue3组件的初始化流程既然是看vue组件的初始化流程,咋们先来创建基本的代码,跑跑流程(在app.vue中写入以下内容,来跑流程)...

vue3优雅的设置element-plus的table自动滚动到底部

场景我是需要在table最后添加一行数据,然后把滚动条滚动到最后。查网上的解决方案都是读取html结构,暴力的去获取,虽能解决问题,但是不喜欢这种打补丁的解决方案,我想着官方应该有相关的定义,于是就去...

Vue3为什么推荐使用ref而不是reactive

为什么推荐使用ref而不是reactivereactive本身具有很大局限性导致使用过程需要额外注意,如果忽视这些问题将对开发造成不小的麻烦;ref更像是vue2时代optionapi的data的替...

9、echarts 在 vue 中怎么引用?(必会)

首先我们初始化一个vue项目,执行vueinitwebpackechart,接着我们进入初始化的项目下。安装echarts,npminstallecharts-S//或...

无所不能,将 Vue 渲染到嵌入式液晶屏

该文章转载自公众号@前端时刻,https://mp.weixin.qq.com/s/WDHW36zhfNFVFVv4jO2vrA前言...

vue-element-admin 增删改查(五)(vue-element-admin怎么用)

此篇幅比较长,涉及到的小知识点也比较多,一定要耐心看完,记住学东西没有耐心可不行!!!一、添加和修改注:添加和编辑用到了同一个组件,也就是此篇文章你能学会如何封装组件及引用组件;第二能学会async和...

最全的 Vue 面试题+详解答案(vue面试题知识点大全)

前言本文整理了...

基于 vue3.0 桌面端朋友圈/登录验证+60s倒计时

今天给大家分享的是Vue3聊天实例中的朋友圈的实现及登录验证和倒计时操作。先上效果图这个是最新开发的vue3.x网页端聊天项目中的朋友圈模块。用到了ElementPlus...

不来看看这些 VUE 的生命周期钩子函数?| 原力计划

作者|huangfuyk责编|王晓曼出品|CSDN博客VUE的生命周期钩子函数:就是指在一个组件从创建到销毁的过程自动执行的函数,包含组件的变化。可以分为:创建、挂载、更新、销毁四个模块...

Vue3.5正式上线,父传子props用法更丝滑简洁

前言Vue3.5在2024-09-03正式上线,目前在Vue官网显最新版本已经是Vue3.5,其中主要包含了几个小改动,我留意到日常最常用的改动就是props了,肯定是用Vue3的人必用的,所以针对性...

Vue 3 生命周期完整指南(vue生命周期及使用)

Vue2和Vue3中的生命周期钩子的工作方式非常相似,我们仍然可以访问相同的钩子,也希望将它们能用于相同的场景。...

救命!这 10 个 Vue3 技巧藏太深了!性能翻倍 + 摸鱼神器全揭秘

前端打工人集合!是不是经常遇到这些崩溃瞬间:Vue3项目越写越卡,组件通信像走迷宫,复杂逻辑写得脑壳疼?别慌!作为在一线摸爬滚打多年的老前端,今天直接甩出10个超实用的Vue3实战技巧,手把...

怎么在 vue 中使用 form 清除校验状态?

在Vue中使用表单验证时,经常需要清除表单的校验状态。下面我将介绍一些方法来清除表单的校验状态。1.使用this.$refs...

取消回复欢迎 发表评论: