记一次 .NET 某新能源系统 线程疯涨 分析
一:背景
1. 讲故事
前段时间收到一个朋友的求助,说他的程序线程数疯涨,寻求如何解决。
等我分析完之后,我觉得这个问题很有代表性,所以拿出来和大家分享下,还是上老工具 WinDbg。
二: WinDbg 分析
1. 线程真的在疯涨吗
要想查线程有没有疯涨,可以用 !t 命令看一下。
0:000:x86> !t
ThreadCount: 382
UnstartedThread: 1
BackgroundThread: 376
PendingThread: 0
DeadThread: 2
Hosted Runtime: no
Lock
ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 1 59c 00e52fb0 26020 Preemptive 12D67610:00000000 00e4b408 0 STA
2 2 2b30 00e61aa0 2b220 Preemptive 00000000:00000000 00e4b408 0 MTA (Finalizer)
3 3 18cc 00ea72b8 202b220 Preemptive 00000000:00000000 00e4b408 0 MTA
5 4 1f18 00f02998 1020220 Preemptive 00000000:00000000 00e4b408 0 Ukn (Threadpool Worker)
XXXX 6 0 00f056f8 39820 Preemptive 00000000:00000000 00e4b408 0 MTA
6 7 2154 09052448 202b020 Preemptive 12E353E0:00000000 00e4b408 0 MTA
...
377 373 2ee8 21a90958 1029220 Preemptive 12D1FCCC:00000000 00e4b408 0 MTA (Threadpool Worker)
378 374 227c 21b1d510 1029220 Preemptive 12DCBFC8:00000000 00e4b408 0 MTA (Threadpool Worker)
379 375 7e8 21b1baa8 1029220 Preemptive 12D39ADC:00000000 00e4b408 0 MTA (Threadpool Worker)
380 376 1d1c 21a8fec8 1029220 Preemptive 12D11F40:00000000 00e4b408 0 MTA (Threadpool Worker)
381 366 19ec 215c1bd0 1029220 Preemptive 12DB42D8:00000000 00e4b408 0 MTA (Threadpool Worker)
382 377 1dc8 21b1bff0 1029220 Preemptive 12C71F9C:00000000 00e4b408 0 MTA (Threadpool Worker)
383 378 f94 215bc750 1029220 Preemptive 12E10568:00000000 00e4b408 0 MTA (Threadpool Worker)
384 379 17d4 21ac5580 1029220 Preemptive 12D8EE98:00000000 00e4b408 0 MTA (Threadpool Worker)
385 381 2c1c 21b1b018 1029220 Preemptive 12D0DD00:00000000 00e4b408 0 MTA (Threadpool Worker)
386 380 309c 21b1da58 1029220 Preemptive 12E25028:00000000 00e4b408 0 MTA (Threadpool Worker)
387 382 3048 21ac6aa0 1029220 Preemptive 12DFA918:00000000 00e4b408 0 MTA (Threadpool Worker)
从卦中看,主线程是一个 STA,说明是一个窗体程序,一个窗体能做到 387 个线程,也是挺牛的,同时也能观察到大多都是 ThreadPool Worker ,也就是线程池工作线程。
2. 这些线程都在干嘛
这里有一个小技巧,那就是线程号越大,往往都是最新创建的,往往从这里面就能套出来一些有用的东西,言外之意就扒一扒 380 ~ 387 这些线程的调用栈。
0:387:x86> ~387s
ntdll_77380000!NtWaitForSingleObject+0xc:
773f29dc c20c00 ret 0Ch
0:387:x86> k
CvRegToMachine(x86) conversion failure for 0x14f
X86MachineInfo::SetVal: unknown register 0 requested
# ChildEBP RetAddr
00 31fef104 755a1539 ntdll_77380000!NtWaitForSingleObject+0xc
01 31fef104 74b3ee3b KERNELBASE!WaitForSingleObjectEx+0x99
02 31fef168 74b3efed clr!CLRSemaphore::Wait+0xbe
03 31fef19c 74b3eee2 clr!ThreadpoolMgr::UnfairSemaphore::Wait+0x13a
04 31fef204 74a54c27 clr!ThreadpoolMgr::WorkerThreadStart+0x328
05 31feff24 7649fa29 clr!Thread::intermediateThreadProc+0x58
06 31feff34 773e7a7e kernel32!BaseThreadInitThunk+0x19
07 31feff90 773e7a4e ntdll_77380000!__RtlUserThreadStart+0x2f
0:387:x86> ~386s
ntdll_77380000!NtWaitForSingleObject+0xc:
773f29dc c20c00 ret 0Ch
0:386:x86> k
CvRegToMachine(x86) conversion failure for 0x14f
X86MachineInfo::SetVal: unknown register 0 requested
# ChildEBP RetAddr
00 31d6ede4 755a1539 ntdll_77380000!NtWaitForSingleObject+0xc
01 31d6ede4 74b3ee3b KERNELBASE!WaitForSingleObjectEx+0x99
02 31d6ee48 74b3efed clr!CLRSemaphore::Wait+0xbe
03 31d6ee7c 74b3eee2 clr!ThreadpoolMgr::UnfairSemaphore::Wait+0x13a
04 31d6eee4 74a54c27 clr!ThreadpoolMgr::WorkerThreadStart+0x328
05 31d6fb84 7649fa29 clr!Thread::intermediateThreadProc+0x58
06 31d6fb94 773e7a7e kernel32!BaseThreadInitThunk+0x19
07 31d6fbf0 773e7a4e ntdll_77380000!__RtlUserThreadStart+0x2f
0:386:x86> ~385s
ntdll_77380000!NtWaitForSingleObject+0xc:
773f29dc c20c00 ret 0Ch
0:385:x86> k
CvRegToMachine(x86) conversion failure for 0x14f
X86MachineInfo::SetVal: unknown register 0 requested
# ChildEBP RetAddr
00 31eaee64 755a1539 ntdll_77380000!NtWaitForSingleObject+0xc
01 31eaee64 74b3ee3b KERNELBASE!WaitForSingleObjectEx+0x99
02 31eaeec8 74b3efed clr!CLRSemaphore::Wait+0xbe
03 31eaeefc 74b3eee2 clr!ThreadpoolMgr::UnfairSemaphore::Wait+0x13a
04 31eaef64 74a54c27 clr!ThreadpoolMgr::WorkerThreadStart+0x328
05 31eafb7c 7649fa29 clr!Thread::intermediateThreadProc+0x58
06 31eafb8c 773e7a7e kernel32!BaseThreadInitThunk+0x19
07 31eafbe8 773e7a4e ntdll_77380000!__RtlUserThreadStart+0x2f
从线程栈上看,这些线程都在 UnfairSemaphore 处等待,这是一个正常现象,因为这些线程都是通过 UnfairSemaphore 锁来唤醒,不过奇怪的是,这些线程为什么产生,又为什么不被消亡?
根据经验预测:肯定有代码在不断的调度
线程池线程,然后又做了一个短命的操作,导致线程池线程不断新增,又得不到线程可以被消亡的阈值。
3. 程序真的在频繁调度线程吗
既然猜测是程序在频繁的调用线程池线程,能做的只能是观察此时 dump 中的所有线程的线程栈,看能不能挖到点有价值的东西,可以使用 ~*e !clrstack 命令。
经过仔细观察这近400个线程栈,发现有 37 处都是 System.Threading.Thread.Sleep(Int32), 而且大多都是 HslCommunication.Core.Net.NetworkBase.ThreadPoolCheckTimeOut(System.Object) 函数,能清楚的看到是由线程池发起的,接下来就是用 ILSpy 反编译下这个函数看下到底是怎么回事。
protected void ThreadPoolCheckTimeOut(object obj)
{
HslTimeOut hslTimeOut;
if ((hslTimeOut = obj as HslTimeOut) == null)
{
return;
}
while (!hslTimeOut.IsSuccessful)
{
Thread.Sleep(100);
if ((DateTime.Now - hslTimeOut.StartTime).TotalMilliseconds > (double)hslTimeOut.DelayTime)
{
if (!hslTimeOut.IsSuccessful)
{
LogNet?.WriteWarn(ToString(), "Wait Time Out : " + hslTimeOut.DelayTime);
hslTimeOut.Operator?.Invoke();
hslTimeOut.WorkSocket?.Close();
}
break;
}
}
}
接下来通过 ILSpy 查看这个方法的引用,发现有很多处,抽几个如下:
protected OperateResult<TNetMessage> ReceiveMessage<TNetMessage>(Socket socket, int timeOut, TNetMessage netMsg) where TNetMessage : INetMessage
{
...
if (timeOut > 0)
{
ThreadPool.QueueUserWorkItem(ThreadPoolCheckTimeOut, hslTimeOut);
}
...
}
protected OperateResult<Socket> CreateSocketAndConnect(IPEndPoint endPoint, int timeOut)
{
...
ThreadPool.QueueUserWorkItem(ThreadPoolCheckTimeOut, hslTimeOut);
...
}
protected void CreateSocketAndConnect(IPEndPoint endPoint, int timeOut, Action<OperateResult<Socket>> connectCallback)
{
...
ThreadPool.QueueUserWorkItem(ThreadPoolCheckTimeOut, hslTimeOut);
...
}
从上面代码看,确实存在一些商榷的地方,很多的 socket 操作都用线程池来处理 ThreadPoolCheckTimeOut() 函数,而在这个函数内当 hslTimeOut.IsSuccessful =false 的时候,在 if ((DateTime.Now - hslTimeOut.StartTime).TotalMilliseconds > (double)hslTimeOut.DelayTime) 不满足的时间区间内会一直 sleep,这就导致当 socket 请求量上去后,导致很多线程处于 sleep 状态, 线程池又不得不生成更多的线程来处理 ThreadPoolCheckTimeOut() 逻辑。
到这里终于就找到了符合 线程池线程 疯涨的底层逻辑,接下来看看 HslCommunication.dll 为何物,去找一下它的类库声明。
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("HslCommunication")]
[assembly: AssemblyDescription("一个框架库,包含完善的网络通信及日志组件")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("HslCommunication")]
[assembly: AssemblyCopyright("Copyright By Richard.Hu 2018")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("d3710b78-1b32-4d53-9604-0451a795a2f5")]
[assembly: AssemblyFileVersion("5.3.2.0")]
[assembly: AssemblyVersion("5.3.2.0")]
可以看到,这是一个商业组件。
三:总结
由于定位到疑似是 HslCommunication 组件的问题,看了下还是 商业版 , 这就尴尬了,建议的解决办法如下:
1) 短期:
用 ThreadPool.SetMaxThreads 限定线程上限。
2) 长期:
找作者看看有没有最新版,或者到 https://github.com/dathlin/HslCommunication 上提一个 issue,让别人系统性解决一下。

记一次 .NET 某新能源系统 线程疯涨 分析的更多相关文章
- 记一次 .NET 某教育系统API 异常崩溃分析
一:背景 1. 讲故事 这篇文章起源于 搬砖队大佬 的精彩文章 WinDBg定位asp.net mvc项目异常崩溃源码位置 ,写的非常好,不过美中不足的是通览全文之后,总觉得有那么一点不过瘾,就是没有 ...
- 于PsIsSystemThread无论是在线程系统线程标识获得
我一直好奇一个进程的所有线程改变线程标志Terminated mov edi, edi ; IoIsSystemThread push ebp mov ebp, esp mov eax, [ebp+a ...
- 《Windows内核编程》---系统线程和同步事件
系统线程: 在驱动中生成的线程一般是系统线程,系统线程所在的进程名为“System”,用到的内核API函数是: NTSTATUS PsCreateSystemThread( OUT PHANDLE T ...
- linux系统——线程
linux系统线程 1 引言 线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者.传统的Unix也支持线程的概念,但是在一个 ...
- 记一次 .NET 某纺织工厂 MES系统 API 挂死分析
一:背景 1. 讲故事 这个月中旬,有位朋友加我wx求助他的程序线程占有率很高,寻求如何解决,截图如下: 说实话,和不同行业的程序员聊天还是蛮有意思的,广交朋友,也能扩大自己的圈子,朋友说他因为这个b ...
- [转载]Linux 线程实现机制分析
本文转自http://www.ibm.com/developerworks/cn/linux/kernel/l-thread/ 支持原创.尊重原创,分享知识! 自从多线程编程的概念出现在 Linux ...
- JAVA线程池的分析和使用
1. 引言 合理利用线程池能够带来三个好处.第一:降低资源消耗.通过重复利用已创建的线程降低线程创建和销毁造成的消耗.第二:提高响应速度.当任务到达时,任务可以不需要等到线程创建就能立即执行.第三:提 ...
- [转]ThreadPoolExecutor线程池的分析和使用
1. 引言 合理利用线程池能够带来三个好处. 第一:降低资源消耗.通过重复利用已创建的线程降低线程创建和销毁造成的消耗. 第二:提高响应速度.当任务到达时,任务可以不需要等到线程创建就能立即执行. 第 ...
- 线程组ThreadGroup分析详解 多线程中篇(三)
线程组,顾名思义,就是线程的组,逻辑类似项目组,用于管理项目成员,线程组就是用来管理线程. 每个线程都会有一个线程组,如果没有设置将会有些默认的初始化设置 而在java中线程组则是使用类ThreadG ...
随机推荐
- Go 语言 结构体链表
@ 目录 1. 什么是链表 2. 单项链表的基本操作 3. 使用 struct 定义单链表 4. 尾部添加节点 5. 头部插入节点 6. 指定节点后添加新节点 7. 删除节点 1. 什么是链表 链表是 ...
- 超越iTerm! 号称下一代终端神器,功能贼强大!
程序员的一生,用的最多的两个工具,一个是代码编辑器(Code Editor),另外一个就是命令行终端工具(Terminal).这两个工具对于提高开发效率至关重要. 代码编辑器在过去的 40 年里不断进 ...
- 【在下版本,有何贵干?】Dockerfile中 RUN yum -y install vim失败Cannot prepare internal mirrorlist: No URLs in mirrorlist
隐秘的版本问题---- Dockerfile中 RUN yum -y install vim失败Cannot prepare internal mirrorlist: No URLs in mirro ...
- python学习-Day20
目录 今日内容详细 作业讲解 re模块补充说明 findall的优先级查询 通过索引的方式单独获取分组内匹配到的数据 分组之后还可以给组起别名 split的优先级查询 collections模块 具名 ...
- “如何实现集中管理、灵活高效的CI/CD”在线研讨会精彩内容分享
"如何实现集中管理.灵活高效的CI/CD"在线研讨会精彩片段分享 片段主讲人:李培(西瓜刀) 大家好,我是李培.前面听文老师讲DevOps,包括CI/CD 的一些理论,也是挺有 ...
- MySQL常用数据类型及细节
目录 1 整数类型 1.1 可选属性 1.1.1 M 1.1.2 UNSIGNED 1.1.3 ZEROFILL 2 浮点类型 2.1 精度误差 3 定点数类型 3.1 数据精度说明 3.2 类型介绍 ...
- c++:-1
C++第一部分介绍基础:c++:-0,本节介绍C++中函数使用. 函数 函数调用 调用函数需要先声明函数原型 嵌套调用: 参数传递 在函数被调用时才分配形参的存储单元 实参可以是常量.变量或表达式 实 ...
- 审计 Linux 系统的操作行为的 5 种方案对比
点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 很多时候我们为了安全审计或者故障跟踪排错,可能会记录分析 ...
- 使用 .net + blazor 做一个 kubernetes 开源文件系统
背景 据我所知,目前 kubernetes 本身或者其它第三方社区都没提供 kubernetes 的文件系统.也就是说要从 kubernetes 的容器中下载或上传文件,需要先进入容器查看目录结构,然 ...
- muduo源码分析之muduo简单运用
今天不先实现muduo项目,我们先来看下muduo库的基本使用,只有了解了如何用,才能在写代码的时候知道自己写的找个函数是干嘛的,实际上是怎么使用的这个函数.首先说简单点,就是定义一个Server,设 ...