记一次 .NET 某注塑模具系统 CPU爆高分析 澳五机器人

admin2个月前澳五机器人65


一:背景

1. 讲故事

上个月,一位做工业智能制造的朋友找到我,说他们车间配套的注塑模具人机交互监控系统,上线一周就多次出现偶发性CPU爆高,每次持续时间从三五分钟到十几分钟不等,车间中控大屏会出现卡顿,模具状态数据刷新延迟,已经影响到生产线的正常监测。朋友团队有一定的调试基础,折腾了一周没定位到根因,特意找过来帮忙分析。

这次出问题的系统是部署在工业控制计算机上的,硬件配置不算高,只有4核8G的配置,平时只运行监控程序和边缘计算模块,正常负载下CPU使用率一般维持在15%以内,爆高的时候直接冲到95%以上,内存倒是没什么异常。闲话不多说,直接上WinDbg分析。

二:WinDbg 分析

1. 先确认CPU是不是真的爆高

分析问题的第一步永远是用数据说话,不能只听业务方描述,我们先抓了完整的dump文件,载入WinDbg之后先用!tp命令观察线程池状态:

0:000> !tp
CPU utilization: 92%
Worker Thread: Total: 16 Running: 16 Idle: 0 MaxLimit: 2048 MinLimit: 4
Work Request in Queue: 0
--------------------------------------
Number of Timers: 3
--------------------------------------
Completion Port Thread:Total: 3 Free: 3 MaxFree: 8 CurrentLimit: 3 MaxLimit: 1000 MinLimit: 4

再用!cpuid确认一下核心数:

0:000> !cpuid
CP  F/M/S  Manufacturer     MHz
0  6,10,9  <unavailable>  2100
1  6,10,9  <unavailable>  2100
2  6,10,9  <unavailable>  2100
3  6,10,9  <unavailable>  2100

确实四个核心都被拉满了,CPU利用率92%,而且线程池里16个工作线程全部都在跑,没有空闲线程,问题定位思路一下子就清晰了:所有工作线程都被占满了,接下来看看这些线程现在都在做什么。

2. 遍历所有线程看调用栈

要找CPU爆高的根因,最直接的方式就是把所有线程的托管调用栈全部打出来,用命令~*e !clrstack,很快就发现了异常:几乎所有线程的调用栈都停在了同一个方法上,简化之后如下:

OS Thread Id: 0x2a34 (12)
Child SP       IP Call Site
00000012F843C180 00007ffd88a37215 xxx.Injection.Monitor.DataProcessor+<ProcessRealTimeData>d__18.MoveNext()
00000012F843C360 00007ffd88a2f9e2 System.Threading.ThreadPoolWorkQueue.Dispatch()
00000012F843C410 00007ffd9138e3ee System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart()

所有工作线程都卡在了实时数据处理的ProcessRealTimeData方法里,接下来我们看看这个方法的IL,再反编译出代码看看具体逻辑,很快就找到了问题:方法里有一段循环处理模腔压力曲线的代码,存在一个隐性的死循环条件。

系统每秒钟会从注塑机采集200组压力和温度数据,每次采集后都会调用数据归一化方法做预处理,归一化方法里有一段代码是用来裁剪超出边界的异常数据:

while (currentIndex < dataLength)
{
   if (data[currentIndex] > upperBound || data[currentIndex] < lowerBound)
   {
       RemoveAt(data, currentIndex);
   }
   currentIndex++;
}

问题就出在RemoveAt这里:当删除当前索引位置的元素后,原数组长度减一,但是代码还是直接给currentIndex加一,本来逻辑没问题,但是如果连续两个异常数据出现在数组末尾,删除第一个后,currentIndex增加,刚好等于新的数组长度,循环就直接退出了,看起来没错对不对?

那什么时候会死循环呢?如果数组最后一个元素本身就是异常数据,删除之后,数组长度变成currentIndex,这时候循环条件currentIndex < dataLength依然成立吗?不对,原来的dataLength是进循环前就缓存好的,删除元素后没有更新dataLength!所以不管删了多少个元素,dataLength一直是原始长度,currentIndex一直增加,循环永远满足条件,直接进入死循环占满CPU。

3. 为什么是偶发性爆高?

为什么这个问题不是每次都出现,而是偶发性的?其实也很好解释:只有当采集到的实时数据末尾刚好有异常数据的时候,才会触发这个死循环,如果没有异常数据,或者异常数据不在末尾,程序就能正常运行。而注塑机的异常数据本身就是偶发的,只有压力突变超出阈值的时候才会出现,所以对应的CPU爆高也是偶发性的,这也是朋友团队一周都没定位到根因的原因。

三:问题解决与总结

1. 问题修复

找到根因之后修复就非常简单了,两个修改方式:要么把dataLength换成data.Length,每次循环都拿最新的数组长度;要么删除元素后currentIndex不增加,因为下一个元素已经移到当前索引位置了。修改后的代码:

int currentIndex = 0;
while (currentIndex < data.Count)
{
   if (data[currentIndex] > upperBound || data[currentIndex] < lowerBound)
   {
       data.RemoveAt(currentIndex);
   }
   else
   {
       currentIndex++;
   }
}

修复之后上线观察了两周,再也没有出现过CPU爆高的问题,监控系统运行稳定。

2. 总结

这次的CPU爆高其实是一个非常典型的低级错误导致的线上问题,本质就是缓存数组长度的时候没有考虑到数组长度动态变化的场景,刚好触发了边界条件进入死循环。这里也给大家做两个总结:

第一:处理动态修改长度的集合循环,一定要特别注意循环条件和索引计数,非常容易出现边界错误引发死循环,占满CPU。

第二:工业软件的偶发性问题排查,一定要抓到爆高时刻的dump文件,通过遍历所有线程的调用栈,很快就能定位到卡死的方法,比瞎猜瞎试效率高很多。

这次问题也算给朋友团队上了一课,边界条件的测试一定要覆盖到极端场景,不然一个小小的错误就会导致整个系统不可用,影响生产线的正常运行。 


澳五机器人 澳八机器人 河内机器人 加拿大机器人 花开月下机器人 朱雀机器人 速飞机器人 名爵机器人 飞天机器人 BV机器人 涂六飞单机器人 美猴王机器人 大富豪机器人 速讯机器人 五球助手 十球助手

相关文章

离线元强化学习研究进展与核心算法分析

——基于近期论文的速读总结报告引言在强化学习领域,离线元强化学习(Offline Meta RL)作为新兴研究方向,旨在解决传统强化学习在数据效率、泛化能力和安全性方面的挑战。本报告基于2022-20...

Visual Studio 四月更新 —— 云代理集成 澳五机器人

以下是为您翻译的内容:GitHub Copilot 在 Visual Studio 中的智能化能力正随每次发布持续升级。本次更新将云代理集成置于核心位置,让开发者无需离开 IDE 即可开启远程编码会话...

AI时代,重温10大经典排序算法(一)

在人工智能技术重塑编程生态的当下,重温经典排序算法不仅是夯实计算机科学基础的必经之路,更能帮助开发者理解AI工具优化算法的底层逻辑。作为数据处理的核心基石,排序算法广泛应用于搜索引擎、推荐系统、数据分...

澳五机器人 实操项目选型:AI辅助数据库巡检小工具

结合我们之前聊过的AI辅助开发、多Agent协作、DBX数据库工具、Code Review自动化的全链路背景,Vibe Coding不是"跟着感觉写代码"的玄学玩法,而是一套完全贴...

使用NanUI快速创建具有现代用户界面的WinForm应用程序

在传统WinForm开发中,界面设计常受限于原生控件的外观和功能,难以满足现代用户对美观、交互性的需求。NanUI作为一款基于.NET平台的开源框架,通过集成Chromium渲染引擎,让开发者能够使用...

关于猫踩键盘导致乱码问题的汇报总结

近期,公司办公环境中出现多起因宠物猫踩踏键盘而引发电脑乱码的现象,对工作文档处理、数据录入及系统操作造成了一定干扰。经统计,此类事件在开放办公区域发生频率较高,主要源于员工携带宠物上班或周边流浪猫偶尔...