Qt外场软件崩溃问题解决记录

news/2024/9/20 1:30:47/ 标签: qt, 算法, 开发语言

背景介绍

软件为基于Qt和C++开发的桌面应用软件,主要功能为实现用户设备系统的监控功能和其他业务功能,特点是系统中的设备数量很多,且设备状态数据有两种不同协议的上报。

周末外场传来消息,软件一天内出现了四五次闪退。拿到外场发来的崩溃日志,发现是底层状态解析的地方出现了崩溃。

问题分析

这两个函数位于软件数据层,运行在两个不同的子线程中,分别针对ChangeableLengthFrame和AllFrame两种格式的设备状态数据进行解析。

询问现场后得知,服务端软件的AllFrame数据,由查询后上报改为了每10秒钟上报一次,没有做其他改动。评估这个改动的影响:(1)增加了CPU的压力;(2)增加了AllFrame解析和changeLengthFram解析中的资源竞争的概率。

通过分析现场发回来的info级别日志,发现ChangeableLengthFrame的频率达到了1~3毫秒/帧,这相较于之前的几十毫秒/帧也快了非常多。

问题复现和解决

类似这样的崩溃问题,真实运行环境下通常不容易复现,采取猜测-修改-试用的模式,解决问题的成本过高(时间成本、沟通成本、客户负面印象等),因此需要在仿真环境下解决这个问题。

为此我们设计了一个多线程的demo,模仿这两个解析函数的工作,定位崩溃的原因。主要包括几个类,statepoint是解析的模板类,然后是两个解析的子线程类。主要代码如下:

statepoint类

#ifndef STATEPOINT_H
#define STATEPOINT_H#include <QObject>
#include <QVariant>class StatePoint : public QObject
{Q_OBJECT
public:explicit StatePoint(QObject *parent = nullptr);QString id = "";QVariant value;signals:};#endif // STATEPOINT_H

共享资源类

负责初始化map,解析具体数据与苏,存放解析结果。


//输出化map
void ParamList::initMapIdPoint()
{for(int i=1;i<9999;++i){QString id = QString("%1").arg(i,4,10,QLatin1Char('0'));StatePoint *point = new StatePoint;point->value = i;map_IdPoint.insert(id,point);}
}/// 函数说明-插入statepoint的key和value
/// param key,statepoint的id;value,参数值
void ParamList::insertStatePointIdValue(const QString &key, const QVariant &value)
{QWriteLocker locker(&lockMapStatePointIdValue);map_IdValue.insert(key,value);
}//某个参数的解析
QVariant ParamList::parseVar(StatePoint *point)
{if(!point){return QVariant(0);}QVariant pointValue =0;#if 1int pointProportion = 10;pointValue = point->value;if(pointProportion>1){int size = 3;double scale = 1.0/pointProportion;pointValue = QString::number(scale *pointValue.toDouble(),'f',size);}return pointValue;#endif
}

ChangableFrame线程

//ChangableFrame数据解析
void ChangableFrameParse::process()
{int loopNum = 9999;ParamList *paramList = ParamList::getInstance();for(int num=0;num<loopNum;++num){int randomNumber = QRandomGenerator::global()->bounded(1, 9999);QString id = QString("%1").arg(randomNumber,4,10,QLatin1Char('0'));StatePoint *point = nullptr;{if(paramList->map_IdPoint.contains(id)){point = paramList->map_IdPoint.value(id);}else{continue;}{point->value = paramList->parseVar(point);}}if(point){paramList->insertStatePointIdValue(id,point->value);}//        LOG(INFO)<<"ChangableFrameParse:"<<id.toStdString()<<"  "
//                  <<point->value.toString().toStdString();}QThread::usleep(500);
}

AllFrameParse线程

void AllFrameParse::process()
{int loopNum = 9999;ParamList *paramList = ParamList::getInstance();for(int num=0;num<loopNum;++num){int randomNumber = QRandomGenerator::global()->bounded(1, 9999);QString id = QString("%1").arg(randomNumber,4,10,QLatin1Char('0'));StatePoint *point = nullptr;{if(paramList->map_IdPoint.contains(id)){point = paramList->map_IdPoint.value(id);}else{continue;}{point->value = paramList->parseVar(point);}}if(point){paramList->insertStatePointIdValue(point->value);}//        LOG(INFO)<<"AllFrameParse:"<<id.toStdString()<<"  "
//                  <<point->value.toString().toStdString();}QThread::usleep(500);
}

反复运行软件,每次5~30分钟不等,demo软件都出现了崩溃的问题,成功复现了外场软件的崩溃问题。接下来的问题就是怎么样把这段代码改稳定了。我们依次做了以下改动:

  1. 全局操作枷锁。给point->value = paramList->parseVar(point)加锁,重新运行后稳定了很多,但没抗过1个小时又崩溃了。
  2. 将paramList->insertStatePointIdValue(id,point->value)中的point->value改为一个局部变量QVariant。终于可以稳定运行不会崩溃了。

修改后的代码如下:

void ChangableFrameParse::process()
{int loopNum = 9999;ParamList *paramList = ParamList::getInstance();for(int num=0;num<loopNum;++num){int randomNumber = QRandomGenerator::global()->bounded(1, 9999);QString id = QString("%1").arg(randomNumber,4,10,QLatin1Char('0'));StatePoint *point = nullptr;QVariant value ;{if(paramList->map_IdPoint.contains(id)){point = paramList->map_IdPoint.value(id);}else{continue;}{QMutexLocker locker(&paramList->mutexPoint);point->value = paramList->parseVar(point);value = point->value;}}if(point){paramList->insertStatePointIdValue(id,value);}}QThread::usleep(500);
}

问题复盘

第一个问题比较明显,在多线程中操作全局变量时要加锁,这个大家都知道。主要问题在于很多时候代码是由单线程改为了多线程,这时候要及时处理不同线程间共享资源加锁的问题;另外在复杂的业务代码里,找到关键的位置也很重要。

第二个问题,跟Qt的隐式共享机制有关。QVariant类型是隐式共享的,将全局的point->value传入接口后,接口内并没有像普通c++变量一样执行拷贝操作,仍然是在操作全局的point->value,就为后续的多线程操作埋下了隐患。

总结,软件崩溃的问题很容易把开发人员搞崩溃,一是解决起来很困难,二是不解决是肯定不行的,三是不尽快解决也不行呀。解决崩溃问题的关键,一是要有搜集软件运行信息的手段,二是要和现场的人员及其他相关方多多沟通,发现一些有用的线索。在广泛搜集信息的基础上,做合理假设和有效验证,就可以搞定啦。

补充一点,很多时候软件出现意外的结果,还是因为有些知识点超出了我们现有的认知。通过多读书,多看别人的经验,是成本最低的成长途径。通过自己踩坑和碰壁来提高认知,就算不头破血流也得焦头烂额呀!

最后,感谢您的阅读,希望本文对您有所启发和帮助!


http://www.ppmy.cn/news/1433534.html

相关文章

河南灌溉排涝工程设计资质升级步骤与要点

河南灌溉排涝工程设计资质升级是一个系统性的过程&#xff0c;涉及多个关键步骤和要点。以下是对这一过程的详细梳理&#xff1a; 一、前期准备与自我评估 了解资质要求&#xff1a;首先&#xff0c;企业需要深入了解乙级灌溉排涝工程设计资质的具体要求和标准&#xff0c;对…

C语言——小知识和小细节16

一、左旋字符串 例如字符串 abcd &#xff0c;左旋一个就是 bcda &#xff0c;左旋两个就是 cdab 。 方法一&#xff1a;循环 #include <stdio.h> #include <string.h>void func(char* str, int n) {int i 0;int j 0;int len (int)strlen(str);n % len;//超出…

vue 的 diff 算法

vue的diff算法&#xff0c;是其虚拟DOM实现中的核心部分&#xff0c;用于高效地比较新旧虚拟节点&#xff0c;并据此更新实际的DOM&#xff0c; vue的diff算法&#xff0c;基于以下策略来优化性能&#xff1a; 1、同层比较&#xff1a; Vue 的 diff 不会跨层级比较节点&…

iOS ------代理 分类 拓展

代理协议 一&#xff0c;概念&#xff1a; 代理&#xff0c;又称委托代理&#xff08;delegate&#xff09;&#xff0c;是iOS中常用的一种设计模式。顾名思义&#xff0c;它是把某个对象要做的事委托给别的对象去做。那么别的对象就是这个对象的代理&#xff0c;代替它来打理…

javascript(第三篇)原型、原型链、继承问题,使用 es5、es6实现继承,一网打尽所有面试题

没错这是一道【去哪儿】的面试题目&#xff0c;手写一个 es5 的继承&#xff0c;我又没有回答上来&#xff0c;很惭愧&#xff0c;我就只知道 es5 中可以使用原型链实现继承&#xff0c;但是代码一行也写不出来。 关于 js 的继承&#xff0c;是在面试中除了【 this 指针、命名提…

无人机+光电吊舱:4K AI 180倍混合变焦吊舱技术详解

无人机搭载吊舱是一种常见的配置方式&#xff0c;吊舱可以装载不同的设备&#xff0c;以满足不同的任务需求。吊舱通常挂载在无人机的下方或侧面&#xff0c;可以根据需要进行调整。 随着无人机技术的飞速进步&#xff0c;4K AI 180倍混合变焦吊舱技术的出现&#xff0c;将无人…

C++进阶--异常

C语言传统的处理方式 终止程序&#xff1a;在发生错误时直接终止程序的运行&#xff0c;可以通过assert宏来进行实现。如assert(condition)&#xff0c;其中condition不满足要求时&#xff0c;将会使程序立刻停止执行&#xff0c;并输出相关错误信息。这种方式的确定是用户很难…

计算机网络4——网络层1

文章目录 一、网络层1、概念2、网络层的两个层面1&#xff09;介绍2&#xff09;问题3&#xff09;解决 二、网际协议IP1、介绍2、虚拟互联网络1&#xff09;介绍2&#xff09;案例 3、IP地址1&#xff09;IP 地址及其表示方法2&#xff09;分类的IP地址3&#xff09;无分类编址…

视频滚动字幕一键批量轻松添加,解锁高效字幕编辑,提升视频质量与观众体验

视频已成为我们获取信息、娱乐休闲的重要渠道。一部成功的视频作品&#xff0c;除了画面精美、音质清晰外&#xff0c;字幕的添加也是至关重要的一环。字幕不仅能增强视频的观感&#xff0c;还能提升信息的传达效率&#xff0c;让观众在享受视觉盛宴的同时&#xff0c;更加深入…

探究C++20协程(5)——基于挂起实现无阻塞的定时器

实现目标 当用传统的线程 sleep 函数来让程序等待时&#xff0c;实际上是在阻塞当前线程。阻塞意味着这个线程在指定的时间&#xff08;例如100毫秒&#xff09;内无法执行任何其他任务。这种方式虽然简单&#xff0c;但效率低下&#xff0c;因为它导致CPU资源在等待期间未被充…

使用uni-app开发app时遇到mqtt.js不可用的问题

使用uni-app开发app时遇到mqtt.js不可用的问题 1 问题背景 基于 Vue3 版本创建了 uni-app 项目用于开发微信小程序&#xff0c;项目中用到了 mqtt.js&#xff08;v4.1.0&#xff09;&#xff0c;编译为微信小程序能够正常运行&#xff0c;但是编译为 APP 后&#xff0c;控制台…

C# winform OpenProtocol中数据中的UI是什么类型?

C# winform OpenProtocol中数据中的UI是什么类型&#xff1f;

Nginx安装withSSL模块

Nginx安装withSSL模块 Nginx 配置文件&#xff0c;开启ssl访问时&#xff0c;报出错误信息&#xff1a; nginx: [emerg] the “ssl” parameter requires ngx_http_ssl_module in /usr/local/nginx/conf/nginx_proxy.mimvp.com.conf:76 原因分析: nginx缺少http_ssl_module…

Unity系统学习笔记

文章目录 1.基础组件的认识1.0.组件继承关系图1.1.项目工程文件结构&#xff0c;各个文件夹都是做什么的&#xff1f;1.2.物体变化组件1.2.3.三维向量表示方向1.2.4.移动物体位置附录&#xff1a;使用变换组件实现物体WASD移动 1.3.游戏物体和组件的显示和禁用1.3.1.界面上的操…

数据结构 - 顺序表实现通讯录

test.c文件 #define _CRT_SECURE_NO_WARNINGS 1#include "Contact.h" int main() {Con myContacts;ConInit(&myContacts);int choice;int index;char targetName[100];PerInfo contact; // 创建一个新的联系人信息实例while (1) {printf("\n--- 通讯录管理…

PaddleSeg (2) 模型训练

已处理好数据集和配置文件,可以开始模型训练。 启动训练 python tools/train.py --config configs/xxx.yml --do_eval --use_vdl --save_interval 500 --save_dir output/xxx上述训练命令解释:* `--config`

java spring 07 createBean()和doCreateBean()

01.createBean方法 protected Object createBean(String beanName, RootBeanDefinition mbd, Nullable Object[] args)throws BeanCreationException {if (logger.isTraceEnabled()) {logger.trace("Creating instance of bean " beanName "");}RootBea…

引导过程和服务控制

1、Linux系统开机引导过程 1&#xff09;开机自检 检测硬件设备&#xff0c;找到能够引导系统的设备&#xff0c;比如硬盘 2&#xff09;MBR引导 运行MBR扇区里的主引导程序GRUB 3&#xff09;启动GRUB菜单 系统读取GRUB配置文件(/boot/grub2/grub.cfg)获取内…

spring boot 定义启动页 到 login

当前办法只是针对 项目启动后 直接跳转到 指定静态页面 如果有验证身份 安全等问题 可以另外想办法 去添加 &#xff0c;需要的直接 拉过去使用 修改 【"redirect: 需要启动后访问到文件位置得地址 ”】 直接上代码 &#xff1a; import org.springframework.context…

【教程】使用vitepress搭配githubPages构建自己的在线笔记

1. 创建VitePress项目 确保自己已经安装好了node&#xff0c;我这个笔记用的是node 18.16.0, 怎么安装nvm这个可以csdn或者掘金&#xff0c;再或者等我有空了我就更新一下 使用nvm安装node # 查看可用版本 nvm list avaliable # 安装node nvm install 18.16.0 # 切换node nvm …