摘要:在定位 .NET 应用程序中的高 CPU 占用问题时,WinDbg 是非常强大的工具之一,尤其配合 SOS 扩展使用可以快速锁定“忙线程”或死锁等问题。
在定位 .NET 应用程序中的高 CPU 占用问题时,WinDbg 是非常强大的工具之一,尤其配合 SOS 扩展使用可以快速锁定“忙线程”或死锁等问题。
本文将基于一次实际的分析流程,演示如何一步步定位由线程锁引起的 CPU 高占用。
首先,我们需要加载 SOS.dll。根据你所调试的 .NET 版本不同,使用.loadby指令时的模块名也不同:.loadby sos clr注意:
.NET Framework使用的是clr.dll,所以.loadby sos clr正确;
如果你调试的是 .NET Core或.NET 5+,对应模块可能是coreclr.dll;
可使用 lm命令确认实际加载的模块名。
!runaway这个命令显示自 WinDbg 附加后各线程的 CPU 占用时间。
查看所有线程的调用栈是分析的关键一步。我们使用以下命令:
~* k这会列出所有线程的 原生调用堆栈(native stack)。
关注以下三类线程特征:
持续执行的线程(高 CPU 嫌疑线程)栈顶函数是业务逻辑方法、算法处理、循环等,说明该线程在“忙”,是最需要关注的对象。
卡在等待(阻塞)状态的线程以下函数说明线程被阻塞,可能在等待锁或资源:
WaitForSingleObject
Monitor.Enter
WaitOne
Sleep
找到等待的资源后,看正在等待什么,如果正在等待GC,则继续找谁在GC
如果调用栈中包含以下函数,说明线程正在 GC 中:
clr!GCHeap::GarbageCollect
clr!SVR::gc_heap::gc1
clr!SVR::gc_heap::gc2
clr!SVR::gc_heap::gc3
clr!GCHeap::GarbageCollectGeneration
clr!SVR::GCHeap::GarbageCollect
clr!GCHeap::gc_thread_function
GCInterface::Collect
频繁GC会挂起线程,增加CPU消耗。
4. 分析具体线程
在上一步中,如果你发现某个线程(例如线程 28)调用栈活跃、函数栈持续变化,或者涉及 GC、锁等待,可以使用以下命令聚焦:
~28s !clrstack这将切换到线程 28 并显示它的托管调用栈,便于你进一步确认是否存在如下情况:
死循环或密集计算导致高 CPU;
一直等待某个锁对象,导致其他线程堆积;
某些资源释放不及时,导致线程频繁争抢。
总结通过上述方法,我们可以初步判断线程是否因锁或其他因素导致 CPU 占用异常。在实际排查中,掌握如下三点尤为重要:
先宏观查看所有线程调用栈;
识别忙线程 / 等待线程/ GC线程 ;
进一步使用 !clrstack分析托管调用栈。
这是一种稳定、高效的诊断思路,尤其适用于高 CPU 的 dump 分析场景。
来源:opendotnet