记一次 .NET 某药厂业务系统 CPU爆高分析
一:背景
1. 讲故事
前段时间有位朋友找到我,说他们的程序出现了CPU爆高,让我帮忙看下怎么回事?这种问题好的办法就是抓个dump丢给我,推荐的工具就是用 procdump 自动化抓捕。
二:Windbg 分析
1. CPU 真的爆高吗
还是老规矩,要想找到这个答案,可以使用 !tp 命令。
0:044> !tp
logStart: 1
logSize: 200
CPU utilization: 88 %
Worker Thread: Total: 8 Running: 4 Idle: 4 MaxLimit: 1023 MinLimit: 4
Work Request in Queue: 0
--------------------------------------
Number of Timers: 2
--------------------------------------
Completion Port Thread:Total: 2 Free: 2 MaxFree: 8 CurrentLimit: 2 MaxLimit: 1000 MinLimit: 4
从卦中数据看当前cpu确实达到了 88%,接下来我们观察下这个程序的机器cpu是否给力,可以用 !cpuid 观察。
0:044> !cpuid
CP F/M/S Manufacturer MHz
0 6,94,3 GenuineIntel 3192
1 6,94,3 GenuineIntel 3192
2 6,94,3 GenuineIntel 3192
3 6,94,3 GenuineIntel 3192
从卦中看,尼玛也就4core,有点弱哈,好歹也是一个高利润的药厂,这么抠门哈。
2. 为什么会CPU爆高
导致 CPU 爆高的因素有很多,没有标准答案,需要自己去找原因,首先我们观察下这个程序的线程数量,可以使用 !t 命令即可。
0:044> !t
ThreadCount: 451
UnstartedThread: 0
BackgroundThread: 443
PendingThread: 0
DeadThread: 1
Hosted Runtime: no
Lock
DBG ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 1 22b8 04CE8728 26020 Preemptive 18E5C92C:18E5E4DC 04c86c20 -00001 STA
3 2 17c8 04B25768 2b220 Preemptive 18CAF3A0:18CB1374 04c86c20 -00001 MTA (Finalizer)
4 4 238c 04C0CDD8 202b020 Preemptive 18E45D88:18E464DC 04c86c20 -00001 MTA
5 5 230c 0A6C37A0 202b020 Preemptive 18DAC318:18DAC47C 04c86c20 -00001 MTA
6 6 23a0 0A70E620 202b220 Preemptive 00000000:00000000 04c86c20 -00001 MTA
...
从卦中数据看,当前有 451 个线程,其中后台线程是 443 个,再结合刚才的 !tp 看到的线程池线程也才 8 个,这就说明这个程序中有 400+ 的线程是直接通过 new Thread 创建的,这个信息就比较可疑了,为啥不用线程池用 Thread ,有蹊跷。
接下来的思路就是使用 ~*e !clrstack 命令观察下每个线程此时都在做什么,命令一输入,刷了好久。
0:044> ~*e !clrstack
...
OS Thread Id: 0x220c (18)
Child SP IP Call Site
184CF614 77dd19dc [HelperMethodFrame: 184cf614] System.Threading.Thread.SleepInternal(Int32)
184CF680 141975f4 System.Threading.Thread.Sleep(Int32) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @ 357]
184CF694 165055b9 xxx.ActionThread`1[[xxx]].Loop()
184CF878 74467741 System.Threading.Thread+StartHelper.Callback(System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @ 42]
184CF888 7446fca1 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs @ 183]
184CF8C0 74466742 System.Threading.Thread.StartCallback() [/_/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @ 105]
184CFA14 74cbc29f [DebuggerU2MCatchHandlerFrame: 184cfa14]
...
在卦中的各个线程栈上也没有看到什么特别明显的业务函数,大多都是停在 Thread.SleepInternal 上进行等待,这就让我陷入了迷茫。
3. 一朝顿悟,走出迷茫
CPU不可能无缘无故的爆高,总会是那些线程给抬起来的,但这个程序中的线程大多都在 Thread.SleepInternal 上,若说他们能把 CPU 弄爆总有点说不过去。
但问题总得要解决,在无突破口的情况也只能硬着头皮在 Thread.SleepInternal 上强行突破了,首先用 Ctrl+F 搜下有多少线程卡在 SleepInternal 上,截图如下:

尼玛,几乎所有线程都在 Sleep,一般来说有这么多线程都在 Sleep 也是少数,接下来抽一个线程看看业务方法是怎么进行 Sleep 的,参考代码如下:

在这个Loop方法中我发现有很多的 Sleep(1),看到这个我突然想到了高频的上下文切换导致的 CPU 爆高。
接下来这个代码的指令到底停在哪个方法呢?可以反编译 Loop 方法。
0:047> !clrstack
OS Thread Id: 0xad8 (47)
Child SP IP Call Site
20B5F434 77dd19dc [HelperMethodFrame: 20b5f434] System.Threading.Thread.SleepInternal(Int32)
20B5F4A0 141975f4 System.Threading.Thread.Sleep(Int32) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @ 357]
20B5F4B4 1f123c71 xxx.ActionThread`1[[xxx].Loop()
20B5F698 74467741 System.Threading.Thread+StartHelper.Callback(System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @ 42]
20B5F6A8 1baab7da System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs @ 183]
20B5F6E0 74466742 System.Threading.Thread.StartCallback() [/_/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @ 105]
20B5F834 74cbc29f [DebuggerU2MCatchHandlerFrame: 20b5f834]
0:047> !U /d 1f123c71
Normal JIT generated code
xxx.ActionThread`1[xxx].Loop()
ilAddr is 0A324040 pImport is 08AD6468
Begin 1F123C10, size abd
1f123c10 55 push ebp
1f123c11 8bec mov ebp,esp
1f123c13 57 push edi
1f123c14 56 push esi
1f123c15 81ecd4010000 sub esp,1D4h
1f123c1b c5f877 vzeroupper
1f123c1e c5d857e4 vxorps xmm4,xmm4,xmm4
1f123c22 c5fa7fa524feffff vmovdqu xmmword ptr [ebp-1DCh],xmm4
1f123c2a c5fa7fa534feffff vmovdqu xmmword ptr [ebp-1CCh],xmm4
1f123c32 b850feffff mov eax,0FFFFFE50h
1f123c37 c5fa7f6405f4 vmovdqu xmmword ptr [ebp+eax-0Ch],xmm4
1f123c3d c5fa7f640504 vmovdqu xmmword ptr [ebp+eax+4],xmm4
1f123c43 c5fa7f640514 vmovdqu xmmword ptr [ebp+eax+14h],xmm4
1f123c49 83c030 add eax,30h
...
1f123c5a e84115cc55 call coreclr!JIT_DbgIsJustMyCode (74de51a0)
1f123c5f 90 nop
1f123c60 90 nop
1f123c61 e9300a0000 jmp xxx.ActionThread<xxx>.Loop+0xa86 (1f124696)
1f123c66 90 nop
1f123c67 b901000000 mov ecx,1
1f123c6c e87f54eaea call 09fc90f0 (System.Threading.Thread.Sleep(Int32), mdToken: 06002D01)
>>> 1f123c71 90 nop
...
通过卦中的 >>> 可以确认很多的方法都是在 while (!base.IsTerminated) 中进行空转,如果 Sleep(1) 的线程比较少那可能没什么问题,但也扛不住400多线程一起玩哈,最后高频的上下文切换导致的 CPU 爆高。
在 Sleep(1) 内部会涉及到CPU的等待队列,就绪队列,以及定时器 _KTIMER 内核对象, 因为 Windows 源码不公开,内部还是比较搞的,可以用 !pcr 命令观察下 cpu的背包。
lkd> !pcr 0
KPCR for Processor 0 at fffff8058023c000:
Major 1 Minor 1
NtTib.ExceptionList: fffff80589089fb0
NtTib.StackBase: fffff80589088000
NtTib.StackLimit: 000000137e1fa158
NtTib.SubSystemTib: fffff8058023c000
NtTib.Version: 000000008023c180
NtTib.UserPointer: fffff8058023c870
NtTib.SelfTib: 000000137dfe0000
SelfPcr: 0000000000000000
Prcb: fffff8058023c180
Irql: 0000000000000000
...
CurrentThread: ffff910c66906080
NextThread: 0000000000000000
IdleThread: fffff80583d27a00
DpcQueue:
lkd> dt nt!_KPRCB fffff8058023c180
+0x008 CurrentThread : 0xffff910c`66906080 _KTHREAD
+0x010 NextThread : (null)
+0x018 IdleThread : 0xfffff805`83d27a00 _KTHREAD
...
+0x7c00 WaitListHead : _LIST_ENTRY [ 0xffff910c`5ec30158 - 0xffff910c`628b1158 ]
+0x7c80 DispatcherReadyListHead : [32] _LIST_ENTRY [ 0xfffff805`80243e00 - 0xfffff805`80243e00 ]
上面的[32]就是等待线程的32个优先级的数组队列。
有了上面的分析结果,最后就是告诉朋友做到如下两点:
- 减少 Thread.Sleep(1) 的线程参与数。
- 尽量将 1 -> 50 来缓解,当然越大越好。
三:总结
这次CPU的爆高还是挺有意思,不是业务方法导致的爆高,而是大量的 Sleep(1) 导致的高频上下文切换所致,有点意思,留下此文给大家避坑!
记一次 .NET 某药厂业务系统 CPU爆高分析的更多相关文章
- 记一次 .NET 某医院HIS系统 CPU爆高分析
一:背景 1. 讲故事 前几天有位朋友加 wx 抱怨他的程序在高峰期总是莫名其妙的cpu爆高,求助如何分析? 和这位朋友沟通下来,据说这问题困扰了他们几年,还请了微软的工程师过来解决,无疾而终,应该还 ...
- 记一次 .NET 某医保平台 CPU 爆高分析
一:背景 1. 讲故事 一直在追这个系列的朋友应该能感受到,我给这个行业中无数的陌生人分析过各种dump,终于在上周有位老同学找到我,还是个大妹子,必须有求必应 . 妹子公司的系统最近在某次升级之后, ...
- 记一次 .NET 某智慧物流 WCS系统 CPU 爆高分析
一:背景 1. 讲故事 哈哈,再次见到物流类软件,上个月有位朋友找到我,说他的程序出现了 CPU 爆高,让我帮忙看下什么原因,由于那段时间在苦心研究 C++,分析和经验分享也就懈怠了,今天就给大家安排 ...
- 记一次 .NET游戏站程序的 CPU 爆高分析
一:背景 1. 讲故事 上个月有个老朋友找到我,说他的站点晚高峰 CPU 会突然爆高,发了两份 dump 文件过来,如下图: 又是经典的 CPU 爆高问题,到目前为止,对这种我还是有一些经验可循的. ...
- 记一次 .NET 某旅行社Web站 CPU爆高分析
一:背景 1. 讲故事 前几天有位朋友wx求助,它的程序内存经常飙升,cpu 偶尔飙升,没找到原因,希望帮忙看一下. 可惜发过来的 dump 只有区区2G,能在这里面找到内存泄漏那真有两把刷子..., ...
- 记一次 .NET 某电商交易平台Web站 CPU爆高分析
一:背景 1. 讲故事 已经连续写了几篇关于内存暴涨的真实案例,有点麻木了,这篇换个口味,分享一个 CPU爆高 的案例,前段时间有位朋友在 wx 上找到我,说他的一个老项目经常收到 CPU > ...
- 记一次 .NET 某机械臂智能机器人控制系统MRS CPU爆高分析
一:背景 1. 讲故事 这是6月中旬一位朋友加wx求助dump的故事,他的程序 cpu爆高UI卡死,问如何解决,截图如下: 在拿到这个dump后,我发现这是一个关于机械臂的MRS程序,哈哈,在机械臂这 ...
- 记一次 .NET 某市附属医院 Web程序 偶发性CPU爆高分析
一:背景 1. 讲故事 这个月初,一位朋友加微信求助他的程序出现了 CPU 偶发性爆高,希望能有偿解决一下. 从描述看,这个问题应该困扰了很久,还是医院的朋友给力,开门就是 100块 红包 ,那既然是 ...
- 记一次 .NET 某智能交通后台服务 CPU爆高分析
一:背景 1. 讲故事 前天有位朋友加微信求助他的程序出现了CPU爆高的问题,开局就是一个红包,把我吓懵了! 由于是南方小年,我在老家张罗处理起来不方便,没有第一时间帮他处理,朋友在第二天上午已经找出 ...
- 记一次 .NET 差旅管理后台 CPU 爆高分析
一:背景 1. 讲故事 前段时间有位朋友在微信上找到我,说他的 web 系统 cpu 运行一段时候后就爆高了,让我帮忙看一下是怎么回事,那就看吧,声明一下,我看 dump 是免费的,主要是锤炼自己技术 ...
随机推荐
- 在 Android Studio Java 项目里混合 Kotlin 编程
首先,先搞明白一个概念,这里的 Java 混合 Kotlin 是指文件层级的混合,即 Java 代码还是写在 .java 文件中,Kotlin 代码还是写在 .kt 文件中,只不过是可以在 Java ...
- 文心一言 VS 讯飞星火 VS chatgpt (96)-- 算法导论9.3 1题
一.用go语言,在算法 SELECT 中,输人元素被分为每组 5 个元素.如果它们被分为每组 7个元素,该算法仍然会是线性时间吗?证明:如果分成每组 3 个元素,SELECT 的运行时间不是线性的. ...
- 「codeforces - 868F」Yet Another Minimization Problem
link. 值域分治优化决策单调性 DP 的 trick.朴素做法 trivial,不赘述. 考虑求取一个区间 \([l,r]\) 的 DP 值.先搞定在 \(m=\lfloor\frac{l+r}{ ...
- redis基本数据类型 SortedSet
SortedSet命令练习 将班级的下列学生得分存入Redis的SortedSet中:Jack 85, Lucy 89, Rose 82, Tom 95,Jerry 78, Amy 92, Miles ...
- EtherCAT转Modbus网关做为 MODBUS 从站配置
EtherCAT转Modbus网关做为 MODBUS 从站配置案例 兴达易控EtherCAT转Modbus网关可以用作MODBUS从站的配置.这种网关允许将Modbus协议与EtherCAT协议进行转 ...
- ORA-01008:并非所有变量都已绑定-解决办法
近期批量处理数据,后台用JAVA编写,连接Oracle数据库,程序运行报ORA-01008问题.解决这个问题时遇见的坑较多,下面复盘现象.问题提出解决办法,希望能帮到遇见同类问题的你. 调试问题: 后 ...
- 3DMatch
详细描述链接:3DMatch 数据集.github(介绍非常详细) 官网主页: 主页 3DMatch数据集收集了来自于62个场景的数据,其中54个场景的数据用于训练,8个场景的数据用于评估,其具体名称 ...
- CCF PTA&中国科教工作者协会联合认证
首页 项目简介 条例规定 通知公告 组织机构 服务中心 联系我们 登录|注册 当前位置:首页 > 新闻动态 开放报名:CCF PTA&中国科教工作者协会联合认证 发布时间: 2023-0 ...
- CF1746F Kazaee
prologue 数组范围一定要看好了开,不然容易我一样,调试调了一页多. 还有就是不要傻乎乎地只跑一次和哈希,因为和哈希(从下面地佬的题解中才知道)它其实算作是一种 trick(类比SA(Stimu ...
- King's Tour 题解
King's Tour 题面大意 在 \(n\times m\) 的网格中构造一种从 \((1,1)\) 走到 \((a,b)\) 的方案,要求经过所有格子恰好一次,格子之间八联通. 思路分析 模拟赛 ...