使用Process Explorer查看线程的函数调用堆栈去排查程序高CPU占用问题

news2024/11/25 20:23:37

目录

1、问题描述

2、使用Process Explorer排查软件高CPU占用的一般思路

3、使用Process Explorer工具进行分析

3.1、找到CPU占用高的线程

3.2、查看CPU占用高的线程的函数调用堆栈,找到出问题的代码

3.3、libwebsockets库导出接口lws_service的说明

3.4、解决办法

4、使用Process Explorer查看函数调用堆栈时可能需要pdb符号文件

5、最后


VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具从入门到精通案例集锦(专栏文章正在更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html       前几天使用Process Explorer工具在同事的电脑上排查了一个软件高CPU占用问题,今天正好通过这个实例来完整地讲述如何使用Process Explorer去排查高CPU占用问题。

1、问题描述

       前几天同事在使用我们的软件时,系统出现明显的卡顿,查看Windows资源管理器得知,我们的软件进程占用了将近50%的CPU,所以导致系统出现了较明显的卡顿。查看软件的界面,显示正在重连服务器:

        同事使用的是笔记本电脑,据同事反馈,他之前连的是手机热点(通过手机的移动流量联网),后来他将手机上的热点关闭了,所以笔记本就连不上网络了。软件中连接了多个业务服务器,断网后,与服务器的连接就会陆续断开,软件中会自动发起对各服务器的重连,因为笔记本无法连接外网了,所以软件底层一直在不停地重连。这个高CPU占用可能是服务器自动重连触发的。这个高CPU问题,在这个问题场景下是必现的,简单操作一下就能复现。

2、使用Process Explorer排查软件高CPU占用的一般思路

       我们一般在遇到软件高CPU占用时会优先使用Process Explorer工具来排查。使用该工具可以查看到进程中的所有线程,每个线程占用的CPU比例:

​然后双击CPU占用最高的那个线程,查看线程的函数调用堆栈,对照着源码,一般就能分析出高CPU占用问题了。

       导致高CPU占用一般是程序一直在不停歇的执行代码导致的,不停歇地执行代码的场景主要有以下几种:

1)程序中出现了死循环,程序一直在执行发生死循环的循环体中的代码。
2)线程函数中的循环体(通常是While循环)中没有添加Sleep,导致循环体代码一直在不停歇的执行。

所以排查程序高CPU占用问题时,主要考虑这两个排查方向。

3、使用Process Explorer工具进行分析

3.1、找到CPU占用高的线程

       在出问题的电脑上,打开Process Explorer,在进程列表中找到目标软件进程,双击该进程条目打开进程的属性页面,点击Threads标签页,就能看到线程列表页面。

       一般进程高CPU占用是由某个线程引发的,该线程会有明显的高CPU占用。可以在线程列表中点击表头中的CPU占用列,按照CPU占用比例排序,可以看到某个线程明显占用了较高的CPU,如下所示:

​双击高CPU占用的那个线程条目,查看该线程的函数调用堆栈,如下:

​       有时,我们需要多次点击左下角的refresh刷新按钮,查看多次函数调用堆栈,以查看到有效的函数调用堆栈。一般情况下,代码发生死循环或者线程函数中while循环出现不间断执行代码,使用Process Explorer多次查看到的函数调用堆栈都是类似的或者一样的。

3.2、查看CPU占用高的线程的函数调用堆栈,找到出问题的代码

       通过显示的函数调用堆栈,wsswrapperlib库在调用开源库libwebsockets中的接口。这个wsswrapperlib库是底层协议栈的模块,据了解当前的客户端软件与平台侧的某个服务器就是通过libwebsockets进行通信的,从当前堆栈上看,一直在connect远端的服务器。这个和界面层显示的正在重连服务器是一致的,从堆栈中看到wsswrapperlib模块中的WorkerThread字样,这说明当前的函数调用堆栈就和这个WorkerThread线程有关。

       之前我也遇到过调用libwebsockets的lws_service不当,导致程序高CPU占用的问题,当时问题也是出在自动重连服务器的场景下。对应的问题可以参见我之前写的文章:
使用Process Explorer和Clumsy工具定位软件高CPU占用问题https://blog.csdn.net/chenlycly/article/details/130038272icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/130038272        于是找到维护wsswrapperlib模块的协议栈同事,让他直接在代码中搜索lws_service接口,果然找到了一个线程对应的线程函数WorkerThread,在线程函数中的While循环中调用了lws_service接口:

static void* WorkerThread(void* lpParam)
{
    int n = 0:
    CWSSContext* context = (CWSSContext*)1pParam;
    while (n >=0
#ifdef __ANDROID__
        && context->flag()
#endif
    )
    {
        n = lws_service( context->getContext,50 )
        context->po11();
    }

    return NULL;
}

3.3、libwebsockets库导出接口lws_service的说明

libwebsockets开源库中关于lws_service接口的说明如下:

/**
 * lws_service() - Service any pending websocket activity
 * @context:    Websocket context
 * @timeout_ms:    Timeout for poll; 0 means return immediately if nothing needed
 *        service otherwise block and service immediately, returning
 *        after the timeout if nothing needed service.
 *
 *    This function deals with any pending websocket traffic, for three
 *    kinds of event.  It handles these events on both server and client
 *    types of connection the same.
 *
 *    1) Accept new connections to our context's server
 *
 *    2) Call the receive callback for incoming frame data received by
 *        server or client connections.
 *
 *    You need to call this service function periodically to all the above
 *    functions to happen; if your application is single-threaded you can
 *    just call it in your main event loop.
 *
 *    Alternatively you can fork a new process that asynchronously handles
 *    calling this service in a loop.  In that case you are happy if this
 *    call blocks your thread until it needs to take care of something and
 *    would call it with a large nonzero timeout.  Your loop then takes no
 *    CPU while there is nothing happening.
 *
 *    If you are calling it in a single-threaded app, you don't want it to
 *    wait around blocking other things in your loop from happening, so you
 *    would call it with a timeout_ms of 0, so it returns immediately if
 *    nothing is pending, or as soon as it services whatever was pending.
 */
 
LWS_VISIBLE int
lws_service(struct lws_context *context, int timeout_ms)
{
    return lws_plat_service(context, timeout_ms);
}

上述注释的翻译如下:

This function deals with any pending websocket traffic, for three kinds of event.  It handles these events on both server and client types of connection the same.
1) Accept new connections to our context's server
2) Call the receive callback for incoming frame data received by server or client connections.
此函数处理任何需要处理的(悬而未决的)websocket 流量,适用于三种事件。它以相同的方式处理服务器和客户端连接类型上的这些事件。
1) 接受到我们上下文服务器的新连接
2) 调用回调函数,将服务器或客户端连接接收到的数据,回调出去。

You need to call this service function periodically to all the above functions to happen; if your application is single-threaded you can just call it in your main event loop.
你需要周期性地调用这个服务函数来使上述所有函数发生; 如果您的应用程序是单线程的,您可以在主事件循环中调用它。

从上面的注释可以看出,要保证libwebsockets库中能正常的收发数据,必须要调用lws_service接口。

3.4、解决办法

       在服务器连不上时,调用lws_service函数传递的第二个timeout参数起不到Sleep的作用,我们不管lws_service函数内部会做什么,但lws_service函数会快速的返回,会导致While一直在持续的不停歇地执行,会占用大量的CPU时间片,导致线程占用大量的CPU比例。为了防止出现线程代码不停歇的运行,要在此处人为地添加一个Sleep,代码如下:

static void* WorkerThread(void* lpParam)
{
    int n = 0:
    CWSSContext* context = (CWSSContext*)1pParam;
    while (n >=0
#ifdef __ANDROID__
        && context->flag()
#endif
    )
    {
        n = lws_service( context->getContext,50 )
        context->po11();
        
        Sleep(50);  // 人为地去Sleep一下
    }

    return NULL;
}

一般在业务线程中都要人为地添加Sleep的调用,以防止线程不停歇运行导致高CPU占用问题

4、使用Process Explorer查看函数调用堆栈时可能需要pdb符号文件

       本例中涉及到的接口lws_service,是开源库libwebsockets.dll中的导出接口,导出接口对外部是公开可见的,所以函数调用堆栈中可以直接看到lws_service接口的调用。如果涉及到的接口是库内部的非公开的接口,要在函数调用堆栈中看到详细的函数名,则需要对应模块的pdb符号文件。只需要将pdb符号文件事先放到二进制文件的同级目录(一般是程序的安装目录)中即可。Process Explorer在需要加载pdb文件时,会到二进制文件所在的目录中去搜索pdb文件,搜索到后去自动加载。

       当然,也可以将pdb符号文件统一放在某个目录中,然后将目录设置到Process Explorer中。具体操作方法是,点击Process Explorer菜单栏中的Options -> Configure Symbols...,打开如下的配置窗口:

​配置pdb文件的路径即可。

       还有其他工具可能会用到pdb符号文件,可以参见我之前写的文章:

哪些软件分析工具需要使用到pdb符号文件?icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131574418

5、最后

       本文通过一个具体的实例讲述了如何使用Process Explorer排查程序高CPU占用问题,有一定的参考或借鉴价值。本例中使用Process Explorer查看进程中某线程的函数调用堆栈,Process Explorer工具还有其他的用处,Process Explorer的用途可以总结为:

1)Process Explorer可以查看程序整个进程占用的总的虚拟内存,而Windows任务管理器中看不到,在排查内存泄漏时可以用上。
2)Process Explorer可以看目标程序加载了哪些库以及这些库的路径,还可以看动态加载的dll库有没有加载起来。
3)Process Explorer可以看启动程序时给程序进程传递了哪些命令行参数,比如chrome浏览器运行时会启动多个进程,每个进程负责处理不同的事务,我们可以通过给进程传递的命令行参数得知这个进程是做什么任务的,比如render渲染进程和GPU加速线程。
4)Process Explorer可以看启动程序各个线程的函数调用堆栈,排查死循环、高CPU占用和多线程死锁问题。

我博客中有多个使用Process Explorer排查问题的案例,可以参见专栏《C++软件分析工具案例集锦》中的相关文章:

C++软件分析工具从入门到精通案例集锦(专栏文章正在更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795

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

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

相关文章

200个常用的Python编程相关英语词汇以及它们的中文释义

大家好,我是涛哥。 好多小伙伴反馈说在学习python的过程中,遇到的英文比较多,为自己的学习和开发产生了很大的阻力,所以为大家梳理了一份 Python编程相关常用的英语词汇以及它们的中文释义,当你刚开始学习Python编程的…

SpringBoot整合Easy-ES操作演示文档

文章目录 SpringBoot整合Easy-ES操作演示文档1 概述及特性1.1 官网1.2 主要特性 2 整合配置2.1 导入POM2.2 Yaml配置2.3 EsMapperScan 注解扫描2.4 配置Entity2.5 配置Mapper 3 基础操作3.1 批量保存3.2 数据更新3.3 数据删除3.4 组合查询3.5 高亮查询3.6 统计查询 4 整合异常4…

Java“牵手”天猫商品列表页数据采集+商品价格数据排序,商品销量排序数据,天猫商品API采集方法

天猫商品列表API是天猫平台提供给开发者的应用程序编程接口,通过API可以获取天猫平台上商品列表数据。 天猫商品列表API的使用需要获取Access Token,它代表了访问天猫API的身份认证。 天猫商品列表API的使用步骤如下: 开发者在天猫开发者中…

Dokcer创建MySQL容器,并在宿主机或mysql可视化工具中连接mysql容器的数据库

文章目录 一、Docker 创建 MySQL容器1. 拉取 MySQL 镜像2. 创建并运行 MySQL 容器3. 创建并运行 MySQL 容器(目录映射) 二、连接 MySQL 数据库1. 在 MySQL 容器内,连接MySQL2. 在宿主机连接 MySQL(遇到问题及解决方案)…

2023年9月实时获取地图边界数据方法,省市区县街道多级联动【附实时geoJson数据下载】

首先,来看下效果图 在线体验地址:https://geojson.hxkj.vip,并提供实时geoJson数据文件下载 可下载的数据包含省级geojson行政边界数据、市级geojson行政边界数据、区/县级geojson行政边界数据、省市区县街道行政编码四级联动数据&#xff0…

性能测试 —— Jmeter事务控制器

事务: 性能测试中,事务指的是从端到端,一个完整的操作过程,比如一次登录、一次 筛选条件查询,一次支付等;技术上讲:事务就是由1个或多个请求组成的 事务控制器 事务控制器类似简单控制器&…

力扣:94. 二叉树的中序遍历(Python3)

题目: 给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。 来源:力扣(LeetCode) 链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 示例: 示例 1: 输…

问道管理:突然飙涨!10分钟暴拉10倍

杠杆资金动手了,还是大手笔! Wind数据显现,9月11日融资客净买入255.41亿元,创2020年7月14日以来新高。这一方面因为,9月11日起,融资保证金份额首次下调正式落地,投资者融资买入证券时的融资保证…

通达信指标编写,16进制颜色对照表,妈妈再也不用担心颜色不够用了!!

★★★★★博文创作不易,我的博文不需要打赏,也不需要知识付费,可以白嫖学习小技巧,喜欢的老铁可以多多点赞收藏分享置顶,小红牛在此表示感谢。★★★★★ #龙虎榜小红牛系统,官方微信公众号:g…

机器学习算法详解3:逻辑回归

机器学习算法详解3:逻辑回归 前言 ​ 本系列主要对机器学习上算法的原理进行解读,给大家分享一下我的观点和总结。 本篇前言 ​ 本篇对逻辑回归的算法原理进行解读。 目录结构 文章目录 机器学习算法详解3:逻辑回归1. 引子2. sigmoid函数3. 原…

C++之shared_ptr、unique_ptr、make_shared、make_unique的区别(一百九十七)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生…

vue3之pinia简单使用

一、 Pinia介绍 pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。就是和vuex一样的实现数据共享。 依据Pinia官方文档,Pinia是2019年由vue.js官方成员重新设计的新一代状态管理器,更替Vuex4成为Vuex5。 Pinia 目前也已经是 vue 官方正式…

PackML 学习笔记(2) OPCUA /PackML

2020年11月11日,OPC 基金会发布了PackML 的配套规范(OPC 30050: PackML - Packaging Control)。意味着可以使用OPCUA 信息模型来构建PackML 模型了。 如果写一篇技术简介往往是简单的,要去实现这门技术却很难。首先,OP…

浅谈Dead reckoning实现原理以及常用算法

0. 简介 航位推算是一个很常见的定位方法。在知道当前时刻的位置,然后通过imu等传感器去估计下一个时刻的位置。在自动驾驶车辆定位的时候,GPS提供10Hz的定位信息。这每个GPS信息来临的0.1s的间隔里面,车辆位置也会移动很多。那么这个时候就…

Android.bp语法和使用方法讲解

Android.bp语法和使用方法讲解 Android.bp 文件是什么? Android.bp 文件首先是 Android 系统的一种编译配置文件,是用来代替原来的 Android.mk文件的。在 Android7.0 以前,Android 都是使用 make 来组织各模块的编译,对应的编译…

抖店评价有礼怎么设置|成都瀚网科技

随着电商行业的不断发展和竞争的加剧,如何吸引消费者、提高店铺的口碑成为了每个卖家关注的焦点。其中,抖音电商平台的礼貌评价功能受到广大卖家的青睐。那么,如何设置抖店评论才能有礼貌呢?我们一起来讨论一下吧。 如何设置抖店评…

go-zerogo web集成gorm实战

前言 上一篇:go-zero&go web集成redis实战 从零开始基于go-zero搭建go web项目实战-04集成gorm实战 源码仓库地址 源码 https://gitee.com/li_zheng/treasure-box golang gorm 官网地址:https://gorm.io/zh_CN/docs/index.html GORM介绍 Gorm是…

Postman —— HTTP请求基础组成部分

一般来说,所有的HTTP Request都有最基础的4个部分组成:URL、 Method、 Headers和body。 (1)Method 要选择Request的Method是很简单的,Postman支持所有的请求方式。 (2)URL 要组装一条Request…

Pytorch Advanced(二) Variational Auto-Encoder

自编码说白了就是一个特征提取器,也可以看作是一个降维器。下面找了一张很丑的图来说明自编码的过程。 自编码分为压缩和解码两个过程。从图中可以看出来,压缩过程就是将一组数据特征进行提取, 得到更深层次的特征。解码的过程就是利用之前的…

python-爬虫-urllib

网络爬虫(Web Crawler),又叫网络蜘蛛、网络机器人,是一种自动化数据采集程序 数据采集 → 数据处理 → 数据存储 常见的工作流程如下: 1.定义采集的目标(网站、APP、公众号、小程序)&#xff…