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