第一篇 TTD 专题 :C# 那些短命线程都在干什么?

一:背景1.讲故事在分析的众多dump中,经常会遇到各种奇葩的问题,仅通过dump这种快照形式还是有很多问题搞不定,而通过 perfview 这种粒度又太粗 , 很难找到问题之所在,真的很头疼,比如本篇的 短命线程 问题,参考图如下:

第一篇 TTD 专题 :C# 那些短命线程都在干什么?

文章插图
我们在 t2 时刻抓取的dump对查看 短命线程 毫无帮助 , 我根本就不知道这个线程生前执行了什么代码,为什么这么短命,还就因为这样的短命让 线程池 的线程暴增 。
为了能尽最大努力解决此类问题,武器库中还得再充实一下,比如本系列要聊的 Time Travel Debug,即时间旅行调试 。
二: Time Travel Debug1. 什么是 时间旅行调试如果说 dump 是程序的一张照片,那 TTD 就是程序的一个短视频,很显然短视频的信息量远大于一张照片,因为视频记录着疑难杂症的前因后果,参考价值巨大,简直就是银弹般的存在 。
三:案例演示1. 参考代码这是我曾经遇到的一个真实案例,在没有 TTD 的协助下最终也艰难的找到了问题 , 但如果有 TTD 的协助简直就可以秒杀 , 为了方便说明,先上一个测试代码 。
internal class Program{static void Main(string[] args){for (int i = 0; i < 200; i++){Task.Run(() =>{Test();});}Console.ReadLine();}public static int index = 1;static void Test(){Thread.Sleep(1000);var i = 10;var j = 20;var sum = i + j;Console.WriteLine($"i={index++},sum={sum}");}}程序跑完之后,我们抓一个dump文件,输出如下 。
0:000> !tThreadCount:20UnstartedThread:0BackgroundThread: 7PendingThread:0DeadThread:13Hosted Runtime:noLock DBGIDOSID ThreadOBJState GC ModeGC Alloc ContextDomainCount Apt Exception0112f8 00C4AF2080030220 Preemptive03C3FFAC:03C40000 00c462f8 -00001 Ukn626a70 00C5BBD82b220 Preemptive03C521B8:03C53FE8 00c462f8 -00001 MTA (Finalizer)XXXX40 00C9FEB01039820 Preemptive00000000:00000000 00c462f8 -00001 Ukn (Threadpool Worker)756694 00CA0990302b220 Preemptive03C40314:03C41FE8 00c462f8 -00001 MTA (Threadpool Worker)XXXX60 00CB53B81039820 Preemptive00000000:00000000 00c462f8 -00001 Ukn (Threadpool Worker)XXXX70 00CB59581039820 Preemptive00000000:00000000 00c462f8 -00001 Ukn (Threadpool Worker)XXXX80 00CB43381039820 Preemptive00000000:00000000 00c462f8 -00001 Ukn (Threadpool Worker)XXXX90 00CB4C581039820 Preemptive00000000:00000000 00c462f8 -00001 Ukn (Threadpool Worker)XXXX100 088792781039820 Preemptive00000000:00000000 00c462f8 -00001 Ukn (Threadpool Worker)8115d10 08879E90102b220 Preemptive03C2AC2C:03C2BFE8 00c462f8 -00001 MTA (Threadpool Worker)XXXX120 0887D1F81039820 Preemptive00000000:00000000 00c462f8 -00001 Ukn (Threadpool Worker)XXXX130 0887C0D81039820 Preemptive00000000:00000000 00c462f8 -00001 Ukn (Threadpool Worker)XXXX140 0887AB701039820 Preemptive00000000:00000000 00c462f8 -00001 Ukn (Threadpool Worker)XXXX150 0887B4001039820 Preemptive00000000:00000000 00c462f8 -00001 Ukn (Threadpool Worker)XXXX160 0887D6401039820 Preemptive00000000:00000000 00c462f8 -00001 Ukn (Threadpool Worker)XXXX170 0887A7281039820 Preemptive00000000:00000000 00c462f8 -00001 Ukn (Threadpool Worker)9185658 0887C520102b220 Preemptive03C46684:03C47FE8 00c462f8 -00001 MTA (Threadpool Worker)1019564 0887C968102b220 Preemptive03C4A664:03C4BFE8 00c462f8 -00001 MTA (Threadpool Worker)XXXX200 0887AFB81039820 Preemptive00000000:00000000 00c462f8 -00001 Ukn (Threadpool Worker)113547c 0887A2E02b220 Preemptive03C50008:03C51FE8 00c462f8 -00001 MTA 2. 为什么会有很多短命线程从 windbg 的输出看有很多的 XXX,那原因是什么呢? 还得先观察下代码,可以看到代码会给 ThreadPool 分发 100 次任务,每个任务也就 1s 的运行时间,这样的代码会造成 ThreadPool 的工作线程处理不及继而会产生更多的工作线程,在某一时刻那些 Sleep 后的线程又会规模性唤醒,ThreadPool 为了能够平衡工作者线程,就会灭掉很多的线程,造成 ThreadPool 中的暴涨暴跌现象 。
因果关系是搞清楚了,但对于落地是没有任何帮助的,比如线程列表倒数第二行已死掉的线程:
XXXX200 0887AFB81039820 Preemptive00000000:00000000 00c462f8 -00001 Ukn (Threadpool Worker)你是没法让它起死回生的,对吧?这时候就必须借助 TTD 录制一个小视频 。
3. TTD 录制录制非常简单,选择 Lauch executable (advanced) 项再勾选 Record 即可,截图如下:
第一篇 TTD 专题 :C# 那些短命线程都在干什么?

文章插图
【第一篇 TTD 专题 :C# 那些短命线程都在干什么?】等程序执行完了或者你觉得时机合适再点击 Stop and Debug 停止录制,截图如下:

推荐阅读