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 1
    int 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,就为后续的多线程操作埋下了隐患。

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

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

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

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zw_ggr_2017

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值