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

admin7天前澳五机器人11


一:背景

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机器人 涂六飞单机器人 美猴王机器人 大富豪机器人 速讯机器人 五球助手 十球助手

相关文章

3年没人敢碰的老代码,我用AI重构了它——然后翻车了

一、项目背景与初衷在公司的技术架构中,有一套已搁置三年的老代码,它负责着核心业务模块的底层逻辑支撑。由于代码编写年代久远,缺乏规范的注释与文档,且涉及多语言混合开发,后续接手的技术人员都因维护风险极高...

使用 PHP 和 WebSocket 构建实时聊天应用完整指南 第二部分

用户认证机制设计‌:通过 Session 或 Token 实现用户身份识别与权限控制。消息持久化方案‌:结合 MySQL 存储聊天记录,确保数据不丢失。多房间支持架构‌:实现用户加入/离开房间、房间内...

Claude Code 使用指南(六):企业级定制与生态扩展

引言:从标准化到定制化在前五篇指南中,我们系统介绍了 Claude Code 的基础使用、团队协作和企业级部署。本篇将聚焦企业级定制化需求,深入探讨如何通过扩展机制、模型微调和生态集成,使 Claud...

Oracle索引技术:理论与实操全解析

在数据量激增的今天,数据库查询性能已成为系统瓶颈的核心。Oracle索引技术通过建立数据访问的"快速通道",能将海量数据的检索效率提升数个数量级。然而,索引并非万能钥匙——不当使用...

深度学习进阶(二)从注意力到自注意力

一、注意力机制:让AI学会"聚焦"在深度学习的发展历程中,注意力机制的出现是一次关键突破。它的灵感源于人类的认知习惯——当我们阅读文章时,会自动聚焦关键词;观察画面时,会优先关注核...

FFmpeg开发笔记(九十三)——国产的开源视频美颜工具VideoEditorForAndroid

一、引言随着短视频与直播行业的爆发式增长,实时视频美颜已成为移动端应用的刚需功能。在Android生态中,开源视频编辑工具长期面临美颜效果差、性能消耗高、定制化难等痛点。VideoEditorForA...