皇冠体育寻求亚洲战略合作伙伴,皇冠代理招募中,皇冠平台开放会员注册、充值、提现、电脑版下载、APP下载。

首页科技正文

Allbet:记一次 .NET 某机械臂智能机械人控制系统MRS CPU爆高剖析

admin2021-09-0717

皇冠最新登陆网址

www.22223388.com)实时更新发布最『zui』新最快的皇冠最新登陆代理线路网址、皇冠最新登陆会员线路网址、皇冠最‘zui’新备用登录网址、皇冠最新手机版登录网址。

,

一:靠山

1. 讲故事

这是6月中旬一 yi[位同伙加wx求助dump的故事,他的程序 cpu爆高‘gao’UI卡死,问若何解决(jue),截图如下:

在拿到这个dump后,我发现这是一个关于机械臂的MRS程“cheng”序,哈哈,在机械臂这种智能机械人领域居〖ju〗然尚有 .NET 的用武之地,有点超出我的认知哈,不知道“dao”把员工当兄弟的大强子客栈里可有 .NET 控制的几台机械臂 。

关于界面卡死的问题我这里就不讨论了,只讨论这个cpu爆高的问题若何解决,究竟追这个系列的同伙都{du}被前面那些种种 内存泄露,内存爆涨 弄倦了,换个口味也挺好。

二【er】: Windbg 剖析

1. 征象验证

别人说cpu高,我得〖de〗先用数据证实一下是否真的云云,方式很简朴,用 !tp 下令即可。


0:000> !tp
CPU utilization: 100%
Worker Thread: Total: 151 Running: 151 Idle: 0 MaxLimit: 32767 MinLimit: 4
Work Request in Queue: 1
    AsyncTimerCallbackCompletion TimerInfo@000000001dc25bb0
--------------------------------------
Number of Timers: 0
--------------------------------------
Completion Port Thread:Total: 2 Free: 1 MaxFree: 8 CurrentLimit: 2 MaxLimit: 1000 MinLimit: 4

从卦象上看,有两个值得关注的指标:

  1. CPU utilization: 100%

这注释那时抓dump的谁人时刻,机械的cpu确实为100%,确实对照糟糕,说点题外话,有几位同伙说他想抓这种100%的dump,发现阿里云的远程桌面连不上,太尴尬了。。。

  1. Total: 151 Running: 151

当前线程池的work线程有151个,正在运行的也是151个,这说明什么呢? 说明每一个线程都在忙碌着,同时也预示着当前的线程不够用,急需招人,当前系统绝对有一股力在推着它。

2. 查看线程列表

接下来再看一下当前的线程列{lie}表,使用 !t 下令。


0:000> !t
ThreadCount:      171
UnstartedThread:  1
BackgroundThread: 167
PendingThread:    1
DeadThread:       2
Hosted Runtime:   no
                                                                                                        Lock  
       ID OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
   0    1  7e0 00000000028901c0    26020 Preemptive  00000000049C9360:00000000049C98A8 0000000000602420 1     STA (GC) 
   9    2  df8 00000000028bc850    2b220 Preemptive  0000000000000000:0000000000000000 0000000000602420 0     MTA (Finalizer) 
  11    3  144 000000001fdef570  102a220 Preemptive  0000000000000000:0000000000000000 0000000000602420 0     MTA (Threadpool Worker) 
  14    9  dbc 0000000020703650  202b220 Preemptive  0000000000000000:0000000000000000 0000000000602420 0     MTA 
  15   10  5a4 00000000206d5860    2b220 Preemptive  0000000000000000:0000000000000000 0000000000602420 0     MTA 
  16   11  17c 00000000206df220    2b220 Preemptive  0000000000000000:0000000000000000 0000000000602420 0     MTA 
  17   12  dd4 00000000205e49a0    2b220 Preemptive  00000000049A7A20:00000000049A98A8 0000000000602420 0     MTA 
  18   14  8fc 0000000020495000    2b220 Preemptive  00000000049A5A40:00000000049A78A8 0000000000602420 0     MTA 
  19   17  a84 0000000020817490  202b220 Preemptive  00000000049ADBB0:00000000049AF8A8 0000000000602420 0     MTA 
  ...
 180  167 12b8 0000000026436d70  1021220 Preemptive  0000000000000000:0000000000000000 0000000000602420 0     MTA (Threadpool Worker) 
 181  168 11a4 0000000026437540  1021220 Preemptive  0000000000000000:0000000000000000 0000000000602420 0     MTA (Threadpool Worker) 
 182  169  880 0000000026437d10  1021220 Preemptive  0000000000000000:0000000000000000 0000000000602420 0     MTA (Threadpool Worker) 
 183  170 1334 00000000264384e0  1021220 Preemptive  0000000000000000:0000000000000000 0000000000602420 0     MTA (Threadpool Worker) 
 184  171  278 0000000026438cb0     1400 Preemptive  0000000000000000:0000000000000000 0000000000602420 0     Ukn 

从卦象看:ID=1 的线程有一个 GC 符号,我去,看样子是触发GC了,这里要提醒一下,事情线程在 GC=Workstation 模式下 xia[,是可以充当GC接纳线程的,这和 GC=Server 模(mo)式下是差其余。

3. 查看线程栈

既然是 GC 触发了‘liao’,那就 死马当活马医,根据GC触「chu」发的套路查,基本流程如下:

  1. 调出所有线(xian)程栈,使用 !EEStack 下令。

0:000> !EEStack 
---------------------------------------------
Thread   0
Current frame: ntdll!NtGetContextThread+0xa
Child-SP         RetAddr          Caller, Callee
...

  1. 查找 WaitUntilGCComplete 要害词看有若干线程在守候 GC 接纳【na】

使用 Ctrl+F 检索即可,截图如下:

从图中看:有96个线程在守候GC完成,到这里,我的嘴角已经上扬了,。。。

AllbetGmaing客户端下载

欢迎进入AllbetGmaing客户端下载(www.aLLbetgame.us),欧博官网是欧博集团的官方网站。欧博官网开放Allbet注册、Allbe代理、Allbet电脑客户端、Allbet手机版下载等业务。

  1. 查找 try_allocate_more_space 要害词判断是什么营业逻辑触发了GC

我去,咋回事? 这有头没尾的,既然没有 try_allocate_more_space 要害词也就说明当前的GC也许率不是自动触发的, 那又是谁触发的呢?有点奇葩哈?

4. GC到底是怎么触发的

要想找出谜底,最简朴粗暴的做法就是看下谁人符号为 GC 的线程到底做了什么? 这里使用 !clrstack 即可。


0:000> !clrstack 
OS Thread Id: 0x7e0 (0)
        Child SP               IP Call Site
000000000043e470 00000000778c1fea [InlinedCallFrame: 000000000043e470] System.GC._Collect(Int32, Int32)
000000000043e470 000007feea38ce2a [InlinedCallFrame: 000000000043e470] System.GC._Collect(Int32, Int32)
000000000043e440 000007feea38ce2a System.GC.Collect()
000000000043e4f0 000007fe8bcd29ca xxx.xxx.T_Tick(System.Object, System.EventArgs)
000000000043e520 000007fee3d0ef6f System.Windows.Forms.Timer.OnTick(System.EventArgs)
000000000043e550 000007fee3d076fe System.Windows.Forms.Timer+TimerNativeWindow.WndProc(System.Windows.Forms.Message ByRef)
000000000043e580 000007fee3cea3c3 System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
000000000043e620 000007fee43611f1 DomainBoundILStubClass.IL_STUB_ReversePInvoke(Int64, Int32, Int64, Int64)
000000000043e890 000007feeac1221e [InlinedCallFrame: 000000000043e890] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef)
000000000043e890 000007fee3d6a378 [InlinedCallFrame: 000000000043e890] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef)
000000000043e860 000007fee3d6a378 DomainBoundILStubClass.IL_STUB_PInvoke(MSG ByRef)
000000000043e920 000007fee3cff23e System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr, Int32, Int32)
000000000043ea10 000007fee3cfebd2 System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)
000000000043eab0 000007fee3cfe9df System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)
000000000043eb10 000007fe8b6208a6 xxx.Program.Main()
000000000043ede0 000007feeac16bb3 [GCFrame: 000000000043ede0] 

我去,从线程栈上看,居然是一个 Timer 在手“shou”工挪用 GC.Collect(),这是什么营业,接下来用 ip2md + savemodulexxx.xxx.T_Tick 源码导出来看一看。


0:000> !ip2md 000007fe8bcd29ca
MethodDesc:   000007fe8b50ae90
Method Name:  xxx.xxx.T_Tick(System.Object, System.EventArgs)
Class:        000007fe8b6ac628
MethodTable:  000007fe8b50b080
mdToken:      00000000060002b5
Module:       000007fe8b504118
IsJitted:     yes
CodeAddr:     000007fe8bcd29a0
Transparency: Critical
0:000> !savemodule 000007fe8b504118 D:\dumps\MRS-CPU\T_Tick.dll
3 sections in file
section 0 - VA=2000, VASize=1a85fc, FileAddr=200, FileSize=1a8600
section 1 - VA=1ac000, VASize=5088, FileAddr=1a8800, FileSize=5200
section 2 - VA=1b2000, VASize=c, FileAddr=1ada00, FileSize=200

用 ILSpy 打开 T_Tick.dll,截图如下:

从代码逻辑看,同伙做了 3min 触发一个 GC 的营业逻辑,我不知道这么做是想干嘛,以是就和同伙在wx上交流了下。

5. 真的全是GC背锅吗

着实在我分享过的许多cpu爆高的dump中,有相当一部门是由【you】于频仍触发GC所致,好比大字符串拼接,误用正则表达式 等等,但3min一次的gc就能把cpu搞挂,这要是小白还能忽悠已往,在懂一点的同伙眼里是经不住推敲的,言外之意就是真正的罪魁还没找到。。。 要寻找可疑罪魁,最好的方式就是对所有线程栈举行地毯式搜索,截图如下:

从上图中可以看到,当前{qian}有112个线程卡在 System.Collections.Generic.Dictionary2[[System.Int32, mscorlib],[System.__Canon, mscorlib]].FindEntry(Int32) 处,你一定会《hui》说了,卡住是由于GC触发冻住了所有线程所致,固然这个理论无《wu》需反驳,确实是〖shi〗这样。

我信托有履历的同伙一定会发现一个问题,那就是多线程环境下泛起了一个线程不平安的 Dictionary,我在之前的一篇车联网CPU爆高剖析中也提到了这个问题,它会导致 在 FindEntry 操作时泛起死循环的怪异征〖zheng〗象。

到这里为止,CPU爆高的问题基本也就全找到了。

三:总结

本次cpu爆高事故主要是由于:

  1. 多线程环境下使用了非线程平安的 Dictionary 导致了死循环。

  2. 周期性的挪用 GC.Collection() 让其雪上加‘jia’霜。

找出问题后,解决设施也就简朴了,建议将 Dictioanry 改成 ConcurrentDictionary,同时去掉手工对 GC.Collection() 的挪用。

网友评论