一:背景
1.讲故事
在分析的众多dump中,经常会遇到各种奇葩的问题,仅通过dump这种快照形式还是有很多问题搞不定,而通过 perfview 这种粒度又太粗,很难找到问题之所在,真的很头疼,比如本篇的 短命线程
问题,参考图如下:
我们在 t2 时刻抓取的dump对查看 短命线程
毫无帮助,我根本就不知道这个线程生前执行了什么代码,为什么这么短命,还就因为这样的短命让 线程池 的线程暴增。
为了能尽最大努力解决此类问题,武器库中还得再充实一下,比如本系列要聊的 Time Travel Debug
,即时间旅行调试。
二: Time Travel Debug
1. 什么是 时间旅行调试
如果说 dump 是程序的一张照片,那 TTD 就是程序的一个短视频,很显然短视频的信息量远大于一张照片,因为视频记录着疑难杂症的前因后果,参考价值巨大,简直就是银弹般的存在。
三:案例演示
1. 参考代码
这是我曾经遇到的一个真实案例,在没有 TTD 的协助下最终也艰难的找到了问题,但如果有 TTD 的协助简直就可以秒杀,为了方便说明,先上一个测试代码。
程序跑完之后,我们抓一个dump文件,输出如下。
2. 为什么会有很多短命线程
从 windbg
的输出看有很多的 XXX,那原因是什么呢? 还得先观察下代码,可以看到代码会给 ThreadPool 分发 100 次任务,每个任务也就 1s 的运行时间,这样的代码会造成 ThreadPool 的工作线程处理不及继而会产生更多的工作线程,在某一时刻那些 Sleep 后的线程又会规模性唤醒,ThreadPool 为了能够平衡工作者线程,就会灭掉很多的线程,造成 ThreadPool 中的暴涨暴跌现象。
因果关系是搞清楚了,但对于落地是没有任何帮助的,比如线程列表倒数第二行已死掉的线程:
你是没法让它起死回生的,对吧?这时候就必须借助 TTD 录制一个小视频。
3. TTD 录制
录制非常简单,选择 Lauch executable (advanced)
项再勾选 Record
即可,截图如下:
等程序执行完了或者你觉得时机合适再点击 Stop and Debug
停止录制,截图如下:
稍等片刻,你会得到如下三个文件。
- ConsoleApp101.run 录制文件
- ConsoleApp101.idx 录制的索引文件
- ConsoleApp101.out 日志文件
4. 分析思路
- 找到 tid=20 的 OSID 线程ID
因为此时的 tid=20
的 OSID 已经不存在了,所以用 !tt
在时间刻度上折半查找 OSID
存在的 position。
可以清楚的看到原来是 OSID =145c
及WindbgID=24
有了这个信息不代表此时它正在执行托管方法,所以我们还需要找到这个 145c
是何时出生的?
- 找到当前视频中所有的
ThreadCreated
事件。
可以在 Events 输出信息中检索 id=0x145c
的线程出生信息。
从输出中可以看到, Lifetime 表示这个线程的一生, ActiveTime 则是从线程的Start处开始的,画个图如下:
接下来将进度条调到 !tt 38B21:0
处,那如何看代码进入到托管方法中呢?这个就得各显神通,我知道的有这么几种。
- 使用单步调试
先用 !tt
调整大致范围,然后用 p,pc,pt,t,tc,tt 微调,比如我们这篇的 !tt 94
就能获取到 tid=20
号线程的托管部分。
- 对 compileMethod 方法下断点
C# 的一个特性就是很多方法都是由 JIT 动态编译的,因为很多方法都是未编译,所以遇到编译事件的时候执行流很大概率就在托管层。
标签: # c#
留言评论