C++程序卡死、UI界面卡顿问题的原因分析与总结

news2024/11/29 10:45:30

目录

1、概述

2、软件卡死问题

2.1、死循环

2.2、死锁

3、客户端软件的UI界面卡顿问题

3.1、UI线程在频繁地写日志到文件中,导致UI线程时不时的卡顿

3.2、从网上拷贝的代码中调用Sleep函数,导致UI界面有明显的卡顿

4、总结


VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=MBR7https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=MBR7https://blog.csdn.net/chenlycly/article/details/125529931        本文就平时项目开发过程中遇到的比较典型的软件卡死、UI界面卡顿问题,大概地总结一下引发这些问题的原因及相关排查方法,给大家提供一些思路和参考。

1、概述

        在日常的项目开发与维护的过程中,时常会遇到软件卡死、UI界面卡顿的问题。这类问题,对于有经验的开发人员,排查起来可能相对容易一些;但对于新手或者刚参与项目的人(对现有项目代码及业务不熟悉),则排查起来可能要困难许多。其实这类问题有一些常用的分析思路和排查方法,本文就平时项目中遇到的问题实例,结合日常的排查经验,对引发这些问题的原因及排查方法做一个大概的总结。

2、软件卡死问题

软件卡死直接表现为进程中的某个或多个线程发生卡死。对于包含UI界面的客户端,可能卡死发生在UI主线程,就会表现为UI界面无法点击、无法操作的问题。软件也有可能卡死发生在某个或多个业务线程中,表现为一些软件业务不能正常运转,软件的功能出现问题了。一般线程卡死或堵塞主要是死循环或死锁导致的。下面就来详细地讲述一下死循环和死锁相关内容。

2.1、死循环

       如果代码中出现了死循环,会导致死循环所在的函数一直不返回或者一段时间内不返回,进而导致函数的主调线程发生卡死。

2.1.1、产生死循环的原因分析

        如果程序执行到死循环代码,一般会出现高CPU占用率的情况,因为一直在不间断地执行死循环中的代码。死循环,可能是for或while中的循环条件有问题,也有可能是消息触发的函数调用上的死循环调用,这两类场景我们在实际项目中遇到过。

        对于循环条件中的死循环,可能是手误将条件中大号或小于号,写成了等于号,如下所示:

for ( int i = 0; i = nChannelNum - 1; i++)
{
    // ......
}

这样上述代码就会无限循环下去。正确的代码应该如下所示:

for ( int i = 0; i <= nChannelNum - 1; i++)
{
    // ......
}

       也有可能是循环条件中的变量值是从服务器侧传过来的,可能是个很大的异常值,比如:

for ( int i = 0; i < nChannelNum; i++)
{
    // ......
}

正常情况下,nChannelNum是小于10的一个整数值,结果实际运行时,服务器传过来一个很大的异常值,比如nChannelNum = 1023456;,这样就会导致一段时间内的死循环。

        至于nChannelNum为什么是个很大的异常值,可能是服务器传过来的就是个异常值,也有可能是客户端程序底层收到服务器传过来的Json格式在解析时解析错了。

        还有一种由消息触发的函数调用上的死循环,我们在实际项目中遇到过几次。比如在UI程序中,A函数中调用了接口产生了消息M,然后代码进入消息M的处理函数中,函数中调用B函数,而B函数中又调用了A函数,这样就形成了一个闭环,即产生了死循环。

2.1.2、死循环的排查

        对于死循环问题,如何进行排查呢?发生死循环时,一般会导致进程的CPU占用较高。此外,死循环应该是发生在某个线程中的,所以那个线程的CPU占用会比较高,我们可以使用工具去查看CPU占用高的线程,然后去查看线程的函数调用堆栈,通过函数调用堆栈判断死循环发生在哪个函数中。

        我们可以使用Process Explorer工具去分析问题,也可以使用Windbg去排查。Process Explorer工具比较简便,主要用来查看CPU高的线程,查看线程的函数调用堆栈:

关于Process Explorer如何使用,可以参见我之前的文章:
使用Clumsy和Process Explorer定位软件高CPU占用问题icon-default.png?t=MBR7https://blog.csdn.net/chenlycly/article/details/120931072        Windbg则是附加到目标进程上,除了可以查看线程的函数调用堆栈,还可以查看相关变量的内存。使用Windbg如何分析死循环问题,我后面会写专门的文章去介绍。

2.2、死锁

        代码中出现了多线程死锁(deadlock),某个线程调用申请获取锁的某个函数,但锁的拥有者一直没有释放,导致申请锁代码所在的函数卡住了,导致线程调用的函数一直没有返回,从而导致线程卡住。

2.2.1、多线程死锁场景及多线程锁的类型

        死锁一般发生在多个线程之间,一般会涉及到两个或两个以上的锁。下面我们先大概地讲述一些发生死锁的场景及用于线程间同步的锁的类型。

2.2.1.1、发生死锁的场景说明

         比如当前有两个线程,线程1和线程2;当前有两个锁,锁1和锁2。假设线程1占用了锁1,正在申请锁2,同时线程2占用了锁2,正在申请锁1,两线程都占用了各自的锁,都在申请对方占用的锁,各不相让,如下所示:

这样就导致了死锁,这是个典型的死锁场景。

        还有一个比较典型的场景是,线程1和线程2之间发生了死锁,导致了线程3的死锁。假设线程2占用了线程3要申请的锁3,因为线程1与线程2之间产生了死锁,导致线程2一直在占用锁3,一直没有释放。而线程3的代码进入了要申请锁3的代码中,因为线程2一直在占用锁3不释放,这样也导致了线程3的死锁,如下所示:

2.2.1.2、锁的类型

        此处我们以Windows平台的多线程锁为例来展开。在Windows平台中可以用多个对象来实现多线程间的锁,比如临界区对象、事件对象、互斥量对象、信号量对象等。

        这些对象主要分用户态对象和内核态对象,其中临界区属于用户态对象,事件、互斥量和信号量则属于内核态对象。使用用户态对象的好处是,不用在用户态与内核态之间切换,在效率上相对高一些,所以在Windows平台上用户态的临界区用的比较多一些。使用内核态对象时,大部分程序代码都运行在用户态的,当操作到这些内核态对象时在底层就需要切换到内核态中,完成对应的操作后再返回到用户态代码中。如果代码在用户态和内核态之间频繁的切换,则执行效率上会有损伤。

用户态的临界区锁,只能用于一个进程中的多个线程间的同步。而事件、互斥量和信号量都属于内核态的对象,除了可以用于一个进程中的多个线程的同步,还可以跨进程使用。

2.2.2、死锁问题的排查

        死锁问题可以通过运行日志去排查,也可以使用调试工具去排查。Windows平台主要使用Windbg,如果发生死锁的是用户态的临界区锁,则使用Windbg去分析要容易许多,Windbg默认是在用户态中的。如果要排查内核态锁引发的死锁,则要复杂一些,Windbg需要切入到内核态中去分析。

        分析死锁问题,首先要确定发生死锁的是那几个线程,可以使用Windbg将所有线程的函数调用堆栈打印出来,如果线程卡在EnterCriticalSection或者WaitForSingleObject等函数上,可能是发生死锁的线程,但还要结合业务与代码进行分析。确定发生死锁的线程,根据线程的函数调用堆栈,分析发生死锁的原因,并加以解决。

        关于如何使用Windbg去排查多线程死锁问题,可以查看我之前写的文章: 

使用Windbg分析多线程临界区死锁问题分享icon-default.png?t=MBR7https://blog.csdn.net/chenlycly/article/details/128532743

3、客户端软件的UI界面卡顿问题

        软件因为死循环或死锁,导致软件的某个或多个线程发生卡死,线程直接卡住了,代码没法继续运行了。而此处讲的UI界面卡顿,与软件卡死还是有较大区别的,可能是UI线程执行了比较耗时的操作或者是人为地执行了sleep操作。

        UI界面卡顿问题也是比较常见的。比如软件在使用的过程中会时不时的出现卡顿,再比如用鼠标点击UI界面会有明显的响应延迟、响应不及时的问题。在某些时段里,软件的卡顿是可以理解的,也是在合理的范围内的,比如在软件刚登陆服务器成功时,会到服务器上拉取大量的数据,要处理大量的消息和数据,软件内部会比较忙碌,这个时段的卡顿是正常的,当然这种情况下,我们也要尽量的进行优化,让软件不那么卡顿,或者卡顿的时间稍微短一点。

        此处我们主要讨论一些不太合理的UI界面卡顿问题。这类问题我们举两个之前项目中遇到的问题实例,简单的说明一下。

3.1、UI线程在频繁地写日志到文件中,导致UI线程时不时的卡顿

        UI客户端软件在运行的过程中,会频繁地出现短暂性的卡顿问题,经分析,UI线程在运行过程中频繁地写日志到文件中导致的。写日志到文件中的文件IO操作相对内存操作要慢很多,如果频繁地写日志,会导致函数不能及时返回,导致UI线程会时不时的卡顿。

        解决办法是,UI线程将要写的日志放到缓存中,新开启一个线程,定时检测缓存队列的大小,定时或者队列达到上限就在新开启的线程中将日志缓存队列中将日志写到文件中。

3.2、从网上拷贝的代码中调用Sleep函数,导致UI界面有明显的卡顿

        某个UI客户端软件,测试同事在测试的过程中发现最近几天UI界面操作出现明显不流畅、卡顿问题,所有UI界面的操作都有这样的问题,这是以前从来没有过的。如果是个别界面在频繁处理数据导致UI界面卡顿是可以理解的,但本问题中是所有的UI界面卡顿。

        后来通过历史版本比对法,逐一安装多个版本,确定问题是从某一天开始有的,那应该是前一天提交的代码引发的(我们的自动化编译系统每天都会自动编译版本)。于是详细查看了前一天的代码修改记录,发现是实现定时检测CPU占用率的代码有问题。代码的逻辑是这样的,在UI线程中开启了一个定时器,在定时器响应函数中调用CPU使用率检测函数,而CPU使用率检测函数中调用Sleep函数,如下所示:

void CalCPURate()
{
    // ...
    
    Sleep(500);
    
    // ...
}

因为计算CPU占用率要找一个参考时间段,所以Sleep了一下。

        CPU使用率检测函数在定时器消息中调用的,所以Sleep肯定是在UI线程中Sleep的,执行Sleep时就会将UI线程挂起不执行了,所以就导致了UI界面卡住了。解决办法是,新开启一个线程,将CPU检测代码放到这个新的线程中去执行。

       此处我们还要再强调一点,从网上拷贝的代码块可能不够严谨,有很多缺陷,或者代码编写时考虑的不够全面,我们在使用的时候要认真的走读审核一下,要将可能存在的问题消灭在初始之时。比如代码块中有内存泄露,如果放置在定时任务中执行,结果可能是毁灭性的。再比如本问题中的代码块,其中包含了对Sleep函数的调用,如果事先认真走读一下代码,就知道这样的代码块不能放置到UI线程中执行,就不会有此处我们说的这个问题了。

4、总结

        我们在排查问题的过程中要多思考多总结,在问题中进步,在问题中积累经验,在问题中提高!要搞清楚问题的来龙去脉,哪怕不是自己负责的模块,也要大致的了解一下。文中是根据实际遇到的问题,总结出来的经验,在此详细记录整理一下,给大家提供一定的借鉴和参考。
 

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

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

相关文章

COCO_03 制作COCO格式数据集 dataset 与 dataloader

文章目录1 引言2 pycocotools介绍3 Dataset 构建4 Dataloader 构建4.1 解决batch中tensor维度不一致的打包问题4.2 collate_fn()函数分析AppendixA. convert_coco_poly_maskB. COCO_Transform参考1 引言 在之前的文章中&#xff0c;我们认识了COCO数据集的基本格式https://blo…

【设计模式】创建型模式·工厂模式

设计模式学习之旅(四) 查看更多可关注后查看主页设计模式DayToDay专栏 一.引子 需求&#xff1a;设计一个咖啡店点餐系统。 设计一个咖啡类&#xff08;Coffee&#xff09;&#xff0c;并定义其两个子类&#xff08;美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】&…

NoSQLBooster for MongoDB 8.0.1 Crack

最智能的 MongoDB IDE NoSQLBooster 是 MongoDB Server 3.6-6.0 的跨平台 GUI 工具&#xff0c;它提供内置的 MongoDB 脚本调试器、全面的服务器监控工具、链接流畅查询、SQL 查询、查询代码生成器、任务调度、ES2020 支持和高级 IntelliSense经验。新版本 8.0 现已推出&#x…

Laravel文档阅读笔记-How to Build a Rest API with Laravel: A Beginners Guide①

随着移动端和JavaScript框架的发展&#xff0c;比如React和Vue&#xff0c;Restful风格的API越来越流行。使用Restful风格的好处就是一个后端程序可以与多个版本的前端用户界面关联。 Laravel提供了创建Rest API的环境和生态。 首先得导入依赖包比如Laravel Passport和Larave…

MySQL中给字符串字段加索引

文章目录前言一、前缀索引和普通索引二、前缀索引对覆盖索引的影响三、优化前缀索引前言 学完了MySQL索引部分&#xff0c;我们清楚的认识到给子段添加索引可以快速的进行查询&#xff0c;节约时间。但是索引有很多。那么对于字段怎么加索引&#xff0c;加什么索引。加到索引不…

linux基本功系列之useradd命令实战

文章目录一. useradd 命令介绍二. 语法格式及常用选项三. 参考案例3.1 不加任何参数创建用户3.2 创建不能登录系统且没有家目录的用户3.3 创建一个用户&#xff0c;ID为23333.4 创建一个用户并指定其附加组3.5 创建用户并账户过期时间3.6 与useradd相关的目录文件总结前言&…

InfluxDB的查询优化

首先&#xff0c;在学习influxDB的查询优化之前&#xff0c;我们要先学习下InfluxDB的解释器profiler&#xff08;类似于mysql的Explain语句&#xff0c;不一样的是&#xff0c;sql&#xff0c;hivesql是提前查看执行计划等&#xff0c;Influx是在当前查询的最后一页两张表&…

力扣(LeetCode)382. 链表随机节点(2023.01.15)

给你一个单链表&#xff0c;随机选择链表的一个节点&#xff0c;并返回相应的节点值。每个节点 被选中的概率一样 。 实现 Solution 类&#xff1a; Solution(ListNode head) 使用整数数组初始化对象。 int getRandom() 从链表中随机选择一个节点并返回该节点的值。链表中所有…

WhatsApp居然有3个版本?深度详解区别!外贸圈获客神器用起来!

近两年&#xff0c;外贸圈用WhatsApp来营销获客&#xff0c;越来越火。不少走在前头的外贸人&#xff0c;已经尝到了甜头。但也有不少后来者&#xff0c;站在门外张望的时候&#xff0c;整个人都是蒙圈的。❓听说动不动要整几十个账号&#xff0c;还要花老长时间养号&#xff1…

《Linux Shell脚本攻略》学习笔记-第六章

6.1 简介 你开发应用程序的时间越长&#xff0c;就越能体会到有一个能够跟踪程序修订历史的软件是多重要。 大多数Linux发行版中都包含了Git。如果你的系统中还没有安装&#xff0c;可以通过yum或者apt-get获取。 6.2 创建新的git仓库 git中的所有项目都需要有一个用于保存项目…

MyBatis-Plus字段加密解密

项目创建POM依赖 <dependency><!--MyBatis-Plus 企业级模块--><groupId>com.baomidou</groupId><artifactId>mybatis-mate-starter</artifactId><version>1.2.8</version> </dependency> <!-- https://mvnrepository…

规划之路:SLAM学习经验分享

针对想学SLAM的提问&#xff0c;我觉得我还是有一定的发言权。作为一个刚入坑SLAM一年多的初学者&#xff0c;首先想说的就是这个研究方向比较广&#xff0c;大方向按搭载传感器分为激光SLAM和视觉SLAM两种&#xff0c;激光SLAM搭载激光雷达&#xff0c;视觉SLAM搭载单目、双目…

[NSSRound#6 Team]Web学习

[NSSRound#6 Team]Web学习 文章目录[NSSRound#6 Team]Web学习前言一、[NSSRound#6 Team]check(V1)二、[NSSRound#6 Team]check(Revenge)总结前言 日常做点题娱乐下&#xff0c;刷到了[NSSRound#6 Team]中是三道web题&#xff0c;学习到了不少&#xff0c;记录下知识点。 提示&…

C语言综合练习6:制作贪吃蛇

1 初始化界面 因为还没学QT&#xff0c;我们就使用终端界面替代。 这里我们假设界面中没有障碍物&#xff0c;我们只需要设定界面的高宽就行&#xff0c;这是蛇的移动范围&#xff0c;我们可以写两个宏来规定界面的高宽 新建一个snake.c的文件 #define _CRT_SECURE_NO_WARNIN…

快出数量级的性能是怎样炼成的

前言&#xff1a;今天学长跟大家讲讲《快出数量级的性能是怎样炼成的》&#xff0c;废话不多说&#xff0c;直接上干货~我们之前做过一些性能优化的案例&#xff0c;不算很多&#xff0c;还没有失手过。少则提速数倍&#xff0c;多则数十倍&#xff0c;极端情况还有提速上千倍的…

关于IDEA配置本地tomcat部署项目找不到项目工件的问题解答

文章目录一 原因分析二 解决方案三 具体的操作方法3.1 打开项目结构找到工件3.2 添加具体的工件内容3.3 配置本地tomcat一 原因分析 可能是之前的项目再次打开后&#xff0c;没有及时配置项目结构中的工件信息&#xff0c;导致配置tomcat中看不到工件的信息 二 解决方案 解决…

react组件优化,当父组件数据变化与子组件无关时,控制子组件不重新渲染

首先 我们来建立一个场景 我们创建一个react项目 然后创建一个父组件 这里我要叫 record.jsx 参考代码如下 import React from "react"; import Subset from "./subset";export default class record extends React.Component{constructor(props){super(…

工作的同时,我也在这里做副业

文章目录一、什么是独自开&#xff1f;二、独自开能给我们带来什么利益&#xff1f;三、如何使用独自开&#xff1f;3.1、用户任务报价步骤13.2、用户任务报价步骤2四、未来的愿景一、什么是独自开&#xff1f; 独自开&#xff0c;全称独自开发一套系统&#xff0c;是基于商品…

CTP开发(2)行情模块的开发

我在做CTP开发之前&#xff0c;也参考了不少其他的资料&#xff0c;发现他们都是把行情和交易做在同一个工程里的。我呢之前也做过期货相关的交易平台&#xff0c;感觉这种把行情和交易做在一起的方法缺乏可扩展性。比如我开了多个CTP账户&#xff0c;要同时交易&#xff0c;这…

springMVC的学习拦截器之验证用户登录案例

文章目录实现思路关于环境和配置文件pomspring的配置文件关于idea的通病/常见500错误的避坑实现步骤编写登陆页面编写Controller处理请求编写登录成功的页面编写登录拦截器实现思路 有一个登录页面&#xff0c;需要写一个controller访问页面登陆页面提供填写用户名和密码的表单…