一:背景

1. 讲故事

这篇文章源自于分析一些疑难dump的思考而产生的灵感,在dump分析中经常要寻找的一个答案就是如何找到死亡线程的生前都做了一些什么?参考如下输出:


0:001> !t
ThreadCount: 22
UnstartedThread: 0
BackgroundThread: 1
PendingThread: 0
DeadThread: 20
Hosted Runtime: no
Lock
ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 1 3a74 00efb368 2a020 Preemptive 02F2AF48:00000000 00ec2fa0 1 MTA
5 2 6758 00f07a48 2b220 Preemptive 00000000:00000000 00ec2fa0 0 MTA (Finalizer)
XXXX 3 0 00f31df0 1039820 Preemptive 00000000:00000000 00ec2fa0 0 Ukn (Threadpool Worker)
XXXX 4 0 00f34080 1039820 Preemptive 00000000:00000000 00ec2fa0 0 Ukn (Threadpool Worker)
XXXX 5 0 00f363a8 1039820 Preemptive 00000000:00000000 00ec2fa0 0 Ukn (Threadpool Worker)
XXXX 6 0 00f372e8 1039820 Preemptive 00000000:00000000 00ec2fa0 0 Ukn (Threadpool Worker)
XXXX 7 0 00f39f80 1039820 Preemptive 00000000:00000000 00ec2fa0 0 Ukn (Threadpool Worker)
XXXX 8 0 00f3cbd0 1039820 Preemptive 00000000:00000000 00ec2fa0 0 Ukn (Threadpool Worker)
XXXX 9 0 00f3d128 1039820 Preemptive 00000000:00000000 00ec2fa0 0 Ukn (Threadpool Worker)
XXXX 10 0 00f40630 1039820 Preemptive 00000000:00000000 00ec2fa0 0 Ukn (Threadpool Worker)
XXXX 11 0 00f43310 1039820 Preemptive 00000000:00000000 00ec2fa0 0 Ukn (Threadpool Worker)
XXXX 12 0 00f42db8 1039820 Preemptive 00000000:00000000 00ec2fa0 0 Ukn (Threadpool Worker)
XXXX 13 0 00f49180 1039820 Preemptive 00000000:00000000 00ec2fa0 0 Ukn (Threadpool Worker)
XXXX 14 0 00f4a228 1039820 Preemptive 00000000:00000000 00ec2fa0 0 Ukn (Threadpool Worker)
XXXX 15 0 00f53a28 1039820 Preemptive 00000000:00000000 00ec2fa0 0 Ukn (Threadpool Worker)
XXXX 16 0 00f56598 1039820 Preemptive 00000000:00000000 00ec2fa0 0 Ukn (Threadpool Worker)
XXXX 17 0 00f59180 1039820 Preemptive 00000000:00000000 00ec2fa0 0 Ukn (Threadpool Worker)
XXXX 18 0 00f59b28 1039820 Preemptive 00000000:00000000 00ec2fa0 0 Ukn (Threadpool Worker)
XXXX 19 0 00f5e8a0 1039820 Preemptive 00000000:00000000 00ec2fa0 0 Ukn (Threadpool Worker)
XXXX 20 0 00f5f248 1039820 Preemptive 00000000:00000000 00ec2fa0 0 Ukn (Threadpool Worker)
XXXX 21 0 00f63fc0 1039820 Preemptive 00000000:00000000 00ec2fa0 0 Ukn (Threadpool Worker)
XXXX 22 0 00f66b50 1039820 Preemptive 00000000:00000000 00ec2fa0 0 Ukn (Threadpool Worker)

前面的 XXXX 代表线程已死亡,那谁能告诉我 ID=22 的线程生前执行了什么代码呢?其实去年我写了一篇如何用 WinDbg 去寻找程序中的短命线程。 TTD 专题 (第一篇):C# 那些短命线程都在干什么?

虽然可以用 WinDbg 的 TTD 来解决,但也有很多的限制,诸如:

  • 生产环境不能安装 windbg 或者 安装不上
  • 不能对生产程序进行附加

所以这两点也制约了 TTD 的强大威力,那有没有轻量级以及无侵入的方式洞察呢?最近在看 perfview 的文档,发现完全可以使用内核中Thread 的 ETW相关事件来搞定。

二:Thread 的ETW事件

1. 使用 Thread 的短命线程

如果死亡线程背后没有标记 Threadpool Worker 的话,那就说明是代码自己用 new Thread 创建出来的线程,这种比较简单,观察 Windows Kernel/Thread/Start 或者 Microsoft-Windows-DotNETRunning/Thread/Creating 的ETW事件即可。

接下来写一段简单的案例代码:


internal class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 5000; i++)
{
Test1();
} Console.ReadLine();
}
public static int index = 1; public static void Test1()
{
new Thread(() => { Test2(); }).Start();
} public static void Test2()
{
Thread.Sleep(10); var i = 10;
var j = 20; var sum = i + j; Console.WriteLine($"i={index++},sum={sum}");
}
}

代码非常简单,用 new Thread 创建了一个短命线程,接下来打开 Perfview 使用默认配置,完整的 Command 命令如下:


PerfView.exe "/DataFile:PerfViewData.etl" /BufferSizeMB:256 /StackCompression /CircularMB:500 /ClrEvents:GC,Binder,Security,AppDomainResourceManagement,Contention,Exception,Threading,JITSymbols,Type,GCHeapSurvivalAndMovement,GCHeapAndTypeNames,Stack,ThreadTransfer,Codesymbols,Compilation /NoGui /NoNGenRundown /Merge:True /Zip:True collect

程序跑完后可以用 WinDbg 的 !t 去看看凌乱现场,可以发现有大量的 XXX 线程。


0:008> !t
ThreadCount: 1386
UnstartedThread: 0
BackgroundThread: 2
PendingThread: 0
DeadThread: 1383
Hosted Runtime: no
Lock
DBG ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 1 4114 02CAC9C8 2a020 Preemptive 0559F108:0559FFEC 02c9c488 -00001 MTA
6 2 31b4 02CBA5F0 2b220 Preemptive 00000000:00000000 02c9c488 -00001 MTA (Finalizer)
XXXX 3 0 02CCEC48 39820 Preemptive 00000000:00000000 02c9c488 -00001 Ukn
XXXX 694 0 116C5B18 39820 Preemptive 00000000:00000000 02c9c488 -00001 Ukn
XXXX 695 0 116C0578 39820 Preemptive 00000000:00000000 02c9c488 -00001 Ukn
XXXX 696 0 116C1250 39820 Preemptive 00000000:00000000 02c9c488 -00001 Ukn
XXXX 697 0 116BF8A0 39820 Preemptive 00000000:00000000 02c9c488 -00001 Ukn
XXXX 698 0 116C5F60 39820 Preemptive 00000000:00000000 02c9c488 -00001 Ukn
XXXX 699 0 116C38D8 39820 Preemptive 00000000:00000000 02c9c488 -00001 Ukn
XXXX 700 0 116C74C8 39820 Preemptive 00000000:00000000 02c9c488 -00001 Ukn
...
XXXX 1380 0 115097C0 39820 Preemptive 00000000:00000000 02c9c488 -00001 Ukn
XXXX 1381 0 115079C8 39820 Preemptive 00000000:00000000 02c9c488 -00001 Ukn
XXXX 1382 0 1150B170 39820 Preemptive 00000000:00000000 02c9c488 -00001 Ukn
XXXX 1383 0 1150AD28 39820 Preemptive 00000000:00000000 02c9c488 -00001 Ukn
XXXX 1384 0 11508258 39820 Preemptive 00000000:00000000 02c9c488 -00001 Ukn
XXXX 1385 0 11505BD0 39820 Preemptive 00000000:00000000 02c9c488 -00001 Ukn
7 1386 6114 1150CF68 20220 Preemptive 055A07A8:055A1FEC 02c9c488 -00001 Ukn

收集一会之后停止收集,选中Thread的内核事件 Thread/Start,截图如下:

从卦中可以看到有大量的 Start 事件,我们挑选其中一个观察下线程栈,右键 Open Any Stacks 看看到底是什么代码发出了这个 ETW 事件,截图如下:

从卦中可以清晰的看到,原来是 Main() -> Test1() 方法创建的哈,终于水落石出。

2. 使用 ThreadPool 的短命线程

在真实场景中也有很多代码是用 ThreadPool 创建出来的短命线程,这种短命线程其实有一个特点,那就是曾经有大量的任务进队列,导致 ThreadPool 被迫生成很多的线程来应付,当任务全部被消灭后,ThreadPool 就会把那些被迫生成的线程全部给裁掉

卸磨杀驴,真的好像我们的职场/(ㄒoㄒ)/~~。

所以突破点就是统计下 ThreadPoolEnqueueWork 事件,有了思路之后修改下测试代码。


public static void Test1()
{
Task.Run(() => { Test2(); });
}

这里有一个注意点,程序跑完之后还要稍等一两分钟,就是让ThreadPool把多余的Thread给灭掉,用 windbg 观察到的效果图就是 讲故事 那一节的,停止 perfview 收集后,寻找 ThreadPoolEnqueueWork 事件,截图如下:

从卦中可以看到有大量的 ThreadPoolEnqueueWork 事件,接下来可以选择右键菜单 Save View as Excel 导出到 Excel 中,然后对 Time Msec 进行分组排序,看下哪一个时间段有大量的任务进队列,指标高的时间段自然就是重点怀疑的。

这里要说一点 Time MSecTrace Start Time 基础上的毫秒级偏移值。

举个例子: 4377.032 (4.37s) + 15:56:25.566 = 15:56:29.866

有了这些概念之后,找到问题区域的进队任务,观察下调用栈,大概率也能找到问题,从调用栈来看,原来是 Test1() 所致哈。。。 截图如下:

三:总结

相比WinDbg TTD的重模式,Perfiew真的很轻,而且无侵入性,这两个工具真的是珠联璧合,相得益彰。

PerfView专题 (第十四篇): 洞察那些 C# 代码中的短命线程的更多相关文章

  1. PerfView专题 (第十二篇):对 C# 下的 SDK 类库进行监控(大结局)

    一:背景 本篇是我们系列文章的最后一篇,前面的文章中大多是在 CLR Runtime 以及 OS 层面进行监控来发现各种可疑的程序问题,除了这两个层面,其实我们还可以对 SDK 中一些类进行洞察,比如 ...

  2. 解剖SQLSERVER 第十四篇 Vardecimals 存储格式揭秘(译)

    解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译) http://improve.dk/how-are-vardecimals-stored/ 在这篇文章,我将深入研究 ...

  3. 第十四篇 Integration Services:项目转换

    本篇文章是Integration Services系列的第十四篇,详细内容请参考原文. 简介在前一篇,我们查看了SSIS变量,变量配置和表达式管理动态值.在这一篇,我们使用SQL Server数据商业 ...

  4. Python之路【第十四篇】:AngularJS --暂无内容-待更新

    Python之路[第十四篇]:AngularJS --暂无内容-待更新

  5. 【译】第十四篇 Integration Services:项目转换

    本篇文章是Integration Services系列的第十四篇,详细内容请参考原文. 简介在前一篇,我们查看了SSIS变量,变量配置和表达式管理动态值.在这一篇,我们使用SQL Server数据商业 ...

  6. 跟我学SpringCloud | 第十四篇:Spring Cloud Gateway高级应用

    SpringCloud系列教程 | 第十四篇:Spring Cloud Gateway高级应用 Springboot: 2.1.6.RELEASE SpringCloud: Greenwich.SR1 ...

  7. SpringBoot第二十四篇:应用监控之Admin

    作者:追梦1819 原文:https://www.cnblogs.com/yanfei1819/p/11457867.html 版权声明:本文为博主原创文章,转载请附上博文链接! 引言   前一章(S ...

  8. Egret入门学习日记 --- 第十四篇(书中 5.4~5.6节 内容)

    第十四篇(书中 5.4~5.6节 内容) 书中内容: 总结 5.4节 内容重点: 1.如何编写自定义组件? 跟着做: 重点1:如何编写自定义组件? 文中提到了重要的两点. 好,我们来试试看. 第一步, ...

  9. Spring Cloud第十四篇 | Api网关Zuul

    ​ 本文是Spring Cloud专栏的第十四篇文章,了解前十三篇文章内容有助于更好的理解本文: Spring Cloud第一篇 | Spring Cloud前言及其常用组件介绍概览 Spring C ...

  10. “全栈2019”Java多线程第三十四章:超时自动唤醒被等待的线程

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

随机推荐

  1. LangChain vs Semantic Kernel

    每当向他人介绍 Semantic Kernel, 会得到的第一个问题就是 Semantic Kernel 类似于LangChain吗,或者是c# 版本的LangChain吗? 为了全面而不想重复的回答 ...

  2. java调用https接口导入证书认证

    1.获取证书 浏览器访问需要调用的接口路径 如图导出证书. 2.进入java目录 jre/lib/security 导入证书 keytool -import -alias name -keystore ...

  3. CS144 计算机网络 Lab2:TCP Receiver

    前言 Lab1 中我们使用双端队列实现了字节流重组器,可以将无序到达的数据重组为有序的字节流.Lab2 将在此基础上实现 TCP Receiver,在收到报文段之后将数据写入重组器中,并回复发送方. ...

  4. Xposed框架关于无法在模拟器中下载和激活的问题

    开头 最近xposed不知道出了什么问题,导致安装的时候一直在失败,所以记录下网上参考到的并用于实践中 安装软件 1.模拟器 逍遥游模拟器 安卓7.1 版本.下载地址为: https://www.52 ...

  5. Golang常用语法糖

    1.名字由来 语法糖(Syntactic sugar)的概念是由英国计算机科学家彼得·兰丁提出的,用于表示编程语言中的某种类型的语法,这些语法不会影响功能,但使用起来却很方便.语法糖,也称糖语法,这些 ...

  6. 2021-03-18:给定一个字符串str,只由‘X’和‘.’两种字符构成。‘X’表示墙,不能放灯,也不需要点亮,‘.’表示居民点,可以放灯,需要点亮。如果灯放在i位置,可以让i-1,i和i+1三个位置被点亮。返回如果点亮str中所有需要点亮的位置,至少需要几盏灯。

    2021-03-18:给定一个字符串str,只由'X'和'.'两种字符构成.'X'表示墙,不能放灯,也不需要点亮,'.'表示居民点,可以放灯,需要点亮.如果灯放在i位置,可以让i-1,i和i+1三个位 ...

  7. 2022-03-15:给定一棵树的头节点head,原本是一棵正常的树, 现在,在树上多加了一条冗余的边, 请找到这条冗余的边并返回。

    2022-03-15:给定一棵树的头节点head,原本是一棵正常的树, 现在,在树上多加了一条冗余的边, 请找到这条冗余的边并返回. 答案2022-03-15: 1.指向头,入度没有0的.入度没有2的 ...

  8. AI DevOps | ChatGPT 与研发效能、效率提升(中)

    为啥 ChatGPT 突然火了? 简单概括就是:产品太过惊艳,体验超预期 之前人工智能发展多年,报道最多的也许就是曾经的李世石大战AlphaGo,现实中的特斯拉自动驾驶,还有波士顿动能放出的机器狗.对 ...

  9. Mysql- DDL/DML/DQL/DCL 数据库基本操作语句(持续更新中)

    Mysql基本语法 前言: 在测试项目中经常需要使用到简单的Mysql语句,但是不知道语句结构是什么,经常在百度查来查去: 以下就是总结Mysql常用的基础操作语句: 只需要执行从创建开始执行示例中的 ...

  10. IBM小型机 - AIX6.1系统安装教程

    AIX6.1系统安装教程 由于工作原因,公司让我帮忙部署AIX小型机的系统,在各处找了很多教程,也请教了大佬协助(感谢大佬的帮助),下面以图文的形式总结了AIX 6.1系统的安装过程. 准备工作 硬件 ...