暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

WinDbg 分析 .NET Dump 线程锁问题

chester技术分享 2025-05-26
304

在定位 .NET 应用程序中的高 CPU 占用问题时,WinDbg 是非常强大的工具之一,尤其配合 SOS 扩展使用可以快速锁定“忙线程”或死锁等问题。

本文将基于一次实际的分析流程,演示如何一步步定位由线程锁引起的 CPU 高占用。

1. 加载 SOS 扩展(针对 .NET)

首先,我们需要加载 SOS.dll。根据你所调试的 .NET 版本不同,使用 .loadby
 指令时的模块名也不同:

    .loadby sos clr

    注意:

    • .NET Framework
       使用的是 clr.dll
      ,所以 .loadby sos clr
       正确;

    • 如果你调试的是 .NET Core
       或 .NET 5+
      ,对应模块可能是 coreclr.dll

    • 可使用 lm
       命令确认实际加载的模块名。

    2.查看cpu占用高的线程

       !runaway

      这个命令显示自 WinDbg 附加后各线程的 CPU 占用时间。

      3. 查看每个线程的调用栈

      查看所有线程的调用栈是分析的关键一步。我们使用以下命令:

        ~* k

        这会列出所有线程的 原生调用堆栈(native stack)

        关注以下三类线程特征:

        持续执行的线程(高 CPU 嫌疑线程)

        栈顶函数是业务逻辑方法、算法处理、循环等,说明该线程在“忙”,是最需要关注的对象。

        卡在等待(阻塞)状态的线程

        以下函数说明线程被阻塞,可能在等待锁或资源:

        • WaitForSingleObject

        • Monitor.Enter

        • WaitOne

        • Sleep

        找到等待的资源后,看正在等待什么,如果正在等待GC,则继续找谁在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 分析场景。

          关注获取技术分享

          文章转载自chester技术分享,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

          评论