【C++软件实战问题排查经验分享】UI界面卡顿 | CPU占用高 | GDI对象泄漏 | 线程堵塞 系列问题排查总结

发布于:2025-04-22 ⋅ 阅读:(15) ⋅ 点赞:(0)

目录

1、UI界面卡顿问题排查

2、软件CPU占用高问题排查

3、UI界面显示异常(GDI对象泄漏导致窗口绘制异常)问题排查

4、软件线程堵塞(包含线程死锁)问题排查

5、最后


C++软件异常排查从入门到精通系列教程(核心精品专栏,订阅量已达5000多个,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C/C++实战专栏(重点专栏,专栏文章已更新500多篇,订阅量已达3000多个,欢迎订阅,持续更新中...)https://blog.csdn.net/chenlycly/article/details/140824370C++ 软件开发从入门到实战(重点专栏,专栏文章已更新300多篇,欢迎订阅,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_2276111.html       我们在开发调试C++软件的过程中,时常会遇到软件UI界面卡顿CPU占用高GDI对象泄漏线程堵塞(死锁)这些常见问题,本文对这些问题的分析与排查方法进行详细的总结,并给出相关的实战分析实例,供大家借鉴或参考。

1、UI界面卡顿问题排查

       当UI界面时不时出现卡顿,可能是UI线程执行了不该执行的、比较耗时的操作引起的。我们在项目中遇到过,一个问题场景是在UI线程中向文件写日志,一个问题场景是在UI线程的代码中调用了Sleep函数。

       对于写日志到文件中,会比较耗时,会直接导致UI线程的短暂堵塞。因为写文件时的文件IO操作,相对与内存读写操作会慢很多,一般不能在UI线程中向文件中写日志,应该把写日志的操作放到新开的线程中。我们在项目中曾经遇到过这样的问题,将写日志的代码移到新开的线程中。

       对于在UI线程中调用Sleep函数,当UI线程执行到Sleep函数调用时,UI线程会进入短暂的睡眠状态,UI线程会短暂的挂起不执行,所以会出现UI界面卡顿。我们在项目中遇到的问题场景是这样的,同事为了实现某个功能,从网上拷贝了一段代码,将该段代码直接放置在UI线程中。代码中设置了定时器,定时执行该段代码,而这段代码中调用了Sleep函数:

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

上述代码运行在UI线程中。当UI线程执行到Sleep函数时,UI线程就会被挂起不执行,从而导致UI界面的堵塞。

       这个UI界面时不时卡顿问题,最初是测试人员发现的,他们反馈这个问题是最近才出现的,前端时间是没有的。但负责开发当前软件的同事尝试查了一下源码,并没有找出问题。于是我建议同事用历史版本比对法,在时间上使用二分法,安装不同时间点的版本,看看问题是从哪天出现的,引发问题的代码点应该就在前一天提交的代码中。最后定位了问题,是从网上拷贝的代码中调用了Sleep导致的堵塞。

       关于UI界面卡顿,我之前做过详细的总结,可以查看我的文章:

C++程序卡死、UI界面卡顿问题的原因分析与总结https://blog.csdn.net/chenlycly/article/details/128703097

2、软件CPU占用高问题排查

       软件占用CPU高,一般是因为程序中在不停歇的执行代码,一般是代码发生死循环导致的。代码发生死循环,可能是以下几个原因导致的:

1)在for或while循环中循环条件有问题,导致循环一直结束不了。比如因为手误,将循环条件中的i<=5,错误写成了i=5赋值操作:

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

导致一直跳不出循环。再比如循环条件中引用了一个变量,而这个变量在某种场景下出现了异常大的值:(变量nChannelNum的值是一个异常大的值)

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

导致代码一直在不停的循环。

2)窗口消息引发的死循环。这个在项目中遇到过,在一个窗口消息A的消息响应函数中调用某个函数或者某段代码,这段代码中又产生了窗口消息B,然后窗口消息B的响应函数中又触发了窗口消息A的产生,这样就产生了窗口消息引发的代码死循环调用,引发死循环。

3)和平台服务器业务交互消息引发的死循环。这个在项目中也遇到过,客户端调用了A接口与服务器交互,然后服务器给出了回应消息,然后客户端在处理该回应消息的响应函数中又调用了A接口,这样就形成了循环闭环,从而产生了由业务消息引发的死循环。

       如果软件占用的CPU较高,可能会导致当前电脑占用的总CPU比较高,会导致电脑系统出现卡顿或者明显的卡顿。可以通过查看Windows任务管理器查看软件的CPU占用比例和整个电脑的CPU占用比例。

      对于软件CPU占用高的排查,可以借助Process Explorer、Windbg等工具进行分析。可以用Process Explorer中查看软件进程中所有线程的CPU占用情况,找到占用CPU最高的线程:

双击查看该线程的函数调用堆栈:

通过调用堆栈分析占用CPU高的原因。

       Process Explorer中查看的线程函数调用堆栈可能不准,可以将Windbg附加到软件进程上使用~*kn命令将所有线程的函数调用堆栈打印出来:

然后利用Process Explorer中现实的线程id,到Windbg中找到对应的线程即可。必要时,可以在Windbg中设置断点进行动态调试,看看具体是哪个函数中发生了死循环。

        关于高CPU占用的实战分析案例,我写过多篇文章,可以查看:
使用Process Explorer/Process Hacker和Windbg高效排查C++程序高CPU占用问题https://blog.csdn.net/chenlycly/article/details/140731953使用Process Explorer和Clumsy工具定位软件高CPU占用问题https://blog.csdn.net/chenlycly/article/details/130038272


       在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)

专栏1:该精品技术专栏的订阅量已达到50000多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,已经更新到200篇以上!欢迎订阅!)

C++软件调试与异常排查从入门到精通系列文章汇总https://blog.csdn.net/chenlycly/article/details/125529931

本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的项目问题实战分析实例(很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!

考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!

专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!

专栏2:(本专栏涵盖了C++多方面的内容,是当前重点打造的专栏,订阅量已达30000多个,专栏文章已经更新到500多篇,持续更新中...)

C/C++实战进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html

以多年的开发实战为基础,总结并讲解一些的C/C++基础与项目实战进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域多个方面的内容,包括C++基础及编程要点(模版泛型编程、STL容器及算法函数的使用等)、数据结构与算法、C++11及以上新特性(不仅看开源代码会用到,日常编码中也会用到部分新特性,面试时也会涉及到)、常用C++开源库的介绍与使用、代码分享(调用系统API、使用开源库)、常用编程技术(动态库、多线程、多进程、数据库及网络编程等)、软件UI编程(Win32/duilib/QT/MFC)、C++软件调试技术(排查软件异常的手段与方法、分析C++软件异常的基础知识、常用软件分析工具使用、实战问题分析案例等)、设计模式、网络基础知识与网络问题分析进阶内容等。

专栏3:  

C++常用软件分析工具从入门到精通案例集锦汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795

常用的C++软件辅助分析工具有SPY++、PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro等,本专栏详细介绍如何使用这些工具去巧妙地分析和解决日常工作中遇到的问题,很有实战参考价值!

专栏4:   

VC++常用功能开发汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/124272585

将10多年C++开发实践中常用的功能,以高质量的代码展现出来。这些常用的高质量规范代码,可以直接拿到项目中使用,能有效地解决软件开发过程中遇到的问题。

专栏5: 

C++ 软件开发从入门到精通(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.html

根据多年C++软件开发实践,详细地总结了C/C++软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。


3、UI界面显示异常(GDI对象泄漏导致窗口绘制异常)问题排查

       UI界面显示不正常,窗口绘制异常,可能是软件进程占用的GDI对象总数异常高导致的。这类问题我们在项目中多次遇到了,一般是使用GDI对象绘图时有GDI对象泄漏导致的。GDI对象泄漏是指GDI对象使用完成后,没有调用Delete或者Release相关接口将GDI对象释放,从而产生了泄漏。

       对于GDI对象泄漏问题,们可以借助GDIView工具来辅助排查。当UI界面显示异常时,可以打开系统的任务管理器:

如果我们软件进程的GDI对象异常的高,接近进程10000个上限,那肯定是GDI对象引起的。当进程的GDI对象达到或接近10000个上限时,程序中调用的GDI绘图API函数可能会失败,导致界面绘制失败,从而引发界面显示异常。

       可以使用GDIView查看到底是哪个GDI对象个数异常的高

这样排查起来会比较有针对性。但光知道发生泄漏的GDI对象类型,可能还是较难定位问题。可能软件从上到下有很多模块,不确定GDI对象泄漏发生在哪个模块。此外,可能发生泄漏的代码平时很难被频繁的执行,只是在某种特定的场景下才会被频繁地执行,这种情况比较具有隐蔽性,或者是这个问题隐藏的比较深。这两类比较有代表性的问题场景,我们在项目中都遇到过。

       所以,除了使用GDIView工具之外,可能还需要结合历史版本比对法(下面给出的案例会讲到历史版本比对法),找到发生泄漏的时间点,到svn或git上查看前一天提交的代码或者底层发布过来的模块,可能就能找到引发问题的代码点了。

       之前写过多篇排查GDI对象泄漏的案例,可以去查看这些文章:
使用GDIView工具排查GDI对象泄漏问题(实战分析案例)https://blog.csdn.net/chenlycly/article/details/125399896使用GDIView工具排查GDI对象泄漏案例的若干细节总结(历史版本比对法)https://blog.csdn.net/chenlycly/article/details/141526436使用GDIView工具排查GDI对象泄漏导致C++程序UI界面绘制异常的问题https://blog.csdn.net/chenlycly/article/details/140731065可以到GDIView等工具官网上或者微软MSDN上查看文档化说明去解决问题https://blog.csdn.net/chenlycly/article/details/139565010使用GDI对象绘制UI时需要注意的若干细节问题总结https://blog.csdn.net/chenlycly/article/details/144233359

4、软件线程堵塞(包含线程死锁)问题排查

       线程中调用了某个接口,该接口一直没返回,这样就会导致调用该接口的线程发生堵塞。如果UI线程堵塞了,就会导致UI界面卡死不可操作(无法点击);如果业务线程堵塞了,则会导致业务出现异常(但UI界面不会卡死,UI界面可以正常点击)。导致接口堵塞不返回,可能有以下几个原因

1)多线程死锁:接口中申请了锁,但锁被其他线程占用,导致一直拿不到锁,接口不返回,即产生了多线程死锁。
2)安全软件拦截:接口中执行了写注册表等操作,PC上安装的安全软件可能认为是不安全的,直接将操作拦截了,可能会导致接口不返回,这个场景我们在项目中遇到过。
3)接口中发生了死循环:接口中有for或while循环,因为某种原因到之后导致死循环,导致接口一直不返回。

       如果堵塞的是UI线程,则UI界面不可操作,UI线程卡死了,一般是调用底层的接口没返回导致的,这种堵塞能被用户第一时间察觉。如果堵塞的是业务线程,就要根据UI界面表现现象以及打印日志去判断。比如加入会议后,看不到会议中的视频图像,可能是底层音视频处理模块中绘制视频的业务线程出现了堵塞,没法绘制视频画面。

       对于死循环引发的线程堵塞,会有个典型的特征,会导致高CPU占用,高CPU的排查方法我们上面已经说过了。

       对于安全软件拦截引发的线程堵塞,可以将Windbg附加到出问题的进程上,查看所有线程的调用堆栈,找到目标线程堆栈中调用的函数,看看是否执行了写注册表等安全软件比较敏感的操作,找出可能的原因。必要时可以借助日志去辅助分析。之前就遇到过这类案例,我写了专题文章,可以去查看文章:
使用Windbg排查C++软件安装包安装时被安全防护软件拦截导致安装线程堵塞卡住的问题https://blog.csdn.net/chenlycly/article/details/143670974

文章中有一点需要注意一下,虽然将腾讯电脑管家软件退出了,但其实时防护模块还在后台运行,还是会持续拦截,除非将腾讯电脑管家卸载掉。

       对于多线程死锁,也是将Windbg附加到出问题的进程上,查看所有线程的函数调用堆栈,看看哪些线程调用了WaitForSingleObject等等待函数,结合这些线程堆栈中调用的函数,确定这些线程和哪些业务有关。必要时也要结合日志去排查并最终定位问题。之前写过使用Windbg排查多线程死锁的实战案例,可以去查看我的文章:
使用Windbg分析多线程临界区死锁问题分享https://blog.csdn.net/chenlycly/article/details/128532743

5、最后

      上述内容均是从项目中遇到的实战问题总结归纳出来的,很有实战参考价值,希望能帮到大家。


网站公告

今日签到

点亮在社区的每一天
去签到