当前逻辑线程增加/线程堆栈泄漏

Posted

技术标签:

【中文标题】当前逻辑线程增加/线程堆栈泄漏【英文标题】:current logical threads increasing / thread stack is leaking 【发布时间】:2012-03-11 18:19:14 【问题描述】:

在性能监视器中监控我的 .NET 应用程序我可以看到 .NET CLR LocksAndThreads / # of current logical Threads 随着时间的推移稳步增加(当前为 293),这表明线程堆栈正在泄漏。

我可以找到很多文章告诉我这是问题所在,但没有任何文章告诉我如何找到原因 - 那么我从哪里开始呢? Windbg 能告诉我问题出在哪里吗?

这是我超过 3 小时的性能监视器,告诉我当前的逻辑线程是 150:

这是线程窗口的输出,它并没有告诉我太多,因为我无法访问它们的调用堆栈——它们大多被标记为 [不可用] 或 [在睡眠中,等待或加入] | [外部代码]:

Unflagged       141024  124 Worker Thread   <No Name>       Normal
Unflagged   >   0   0   Unknown Thread  [Thread Destroyed]      
Unflagged       136272  2   Worker Thread   <No Name>       Highest
Unflagged       133060  7   Worker Thread   vshost.RunParkingWindow [Managed to Native Transition]  Normal
Unflagged       136952  10  Main Thread Main Thread [edited].Program.Main   Normal
Unflagged       134544  9   Worker Thread   .NET SystemEvents   [Managed to Native Transition]  Normal
Unflagged       136556  11  Worker Thread   Worker Thread   [edited].MessageService.ProcessJobs.AnonymousMethod__0  Normal
Unflagged       141364  113 Worker Thread   <No Name>   [In a sleep, wait, or join] Normal
Unflagged       140896  0   Worker Thread   [Thread Destroyed]      Normal
Unflagged       136776  19  Worker Thread   <No Name>   [In a sleep, wait, or join] Normal
Unflagged       135704  20  Worker Thread   <No Name>   [In a sleep, wait, or join] Normal
Unflagged       136712  21  Worker Thread   <No Name>   [In a sleep, wait, or join] Normal
Unflagged       134984  22  Worker Thread   <No Name>   [In a sleep, wait, or join] Normal
Unflagged       134660  23  Worker Thread   Worker Thread   [edited].BroadcastService.ProcessJobs.AnonymousMethod__1d   Normal
Unflagged       140224  152 Worker Thread   <No Name>       Normal
Unflagged       140792  157 Worker Thread   <No Name>       Normal
Unflagged       137116  0   Worker Thread   <No Name>       Normal
Unflagged       140776  111 Worker Thread   <No Name>       Normal
Unflagged       140784  0   Worker Thread   [Thread Destroyed]      Normal
Unflagged       140068  145 Worker Thread   <No Name>       Normal
Unflagged       139000  150 Worker Thread   <No Name>       Normal
Unflagged       140828  52  Worker Thread   <No Name>       Normal
Unflagged       137752  146 Worker Thread   <No Name>       Normal
Unflagged       140868  151 Worker Thread   <No Name>       Normal
Unflagged       141324  139 Worker Thread   <No Name>       Normal
Unflagged       140168  154 Worker Thread   <No Name>       Normal
Unflagged       141848  0   Worker Thread   [Thread Destroyed]      Normal
Unflagged       135544  153 Worker Thread   <No Name>       Normal
Unflagged       142260  140 Worker Thread   <No Name>       Normal
Unflagged       141528  142 Worker Thread   <No Name>   [In a sleep, wait, or join] Normal
Unflagged       141344  0   Worker Thread   [Thread Destroyed]      Normal
Unflagged       140096  136 Worker Thread   <No Name>       Normal
Unflagged       141712  134 Worker Thread   <No Name>       Normal
Unflagged       141688  147 Worker Thread   <No Name>       Normal

更新: 从那以后,我将罪魁祸首追踪到了 System.Timers.Timer。即使这个计时器在每个 Elapsed 事件上调用了一个空方法,它仍然会无限期地提高逻辑线程数。只需将计时器更改为 DispatcherTimer 即可解决问题。

如this question 中所述,在Windbg 中运行!dumpheap -type TimerCallback 时看到大量计时器后,我开始查看应用程序中的所有计时器。

我仍然想知道如何通过 Windbg 调试而不是导致我修复的禁用计时器/检查性能/重复方法检测到这一点。 IE。任何可以告诉我是哪个计时器造成问题的东西。

【问题讨论】:

你知道是什么创造了它们,为什么? 我的应用程序有很多移动部件,所以“为什么”会是许多不同的后台任务。我试图找到增加的来源以弄清楚“什么”。 【参考方案1】:

尝试所有长时间运行的操作(100 多毫秒的数据库调用、磁盘或网络访问)以异步运行。

在 .NET 4.5 中使用 async/await 原语指令。

如果从线程池队列中检索排队的任务时没有可用的线程,线程池将增加线程数。如果这种趋势在服务器中继续下去,您可能会以线程池饥饿告终。由于线程池队列中充满了任务,.net 将拒绝更多请求,因此您将处于应用程序可扩展性的极限。

await 指令将在您的应用程序中生成一个工作流,从而释放主线程。长时间运行的操作完成后,线程池中会自动排队一个新任务,让您的应用程序恢复。以这种方式释放和回收线程将使当前逻辑线程的数量保持在最低水平,从而防止线程之间出现饥饿和更多上下文切换。

此外,在 .NET 4.5 中,新算法控制线程池内新线程创建的成本/收益,当趋势增加时,在性能提升和上下文切换之间保持合理的关系。如果您尚未迁移到 4.5,这是您获得的额外好处。

所以第一步是识别你的长期运行的操作,然后使它们异步。

您可以通过将当前逻辑线程数与其他计数器(数据库客户端连接、磁盘 IO 读取等)相关联来验证这一点。如果第一个增加而其他人增加你很可能确定这是问题所在。还要检查操作需要多长时间。一般来说,100 毫秒是一个很好的衡量标准,可以说明您的操作运行时间很长。

希望对您有所帮助。

【讨论】:

【参考方案2】:

这通常是由于线程池线程卡住而无法完成造成的。每半秒,线程池管理器允许另一个线程开始尝试处理积压。这一直持续到达到 ThreadPool.SetMaxThreads() 设置的最大线程数。默认情况下是一个巨大的数字,在 4 核机器上为 1000。

使用 Debug + Windows + Threads 查看正在运行的线程。他们的调用堆栈应该清楚地说明他们阻塞的原因。

【讨论】:

嗨,汉斯。我看了看,但正如上面更新的那样,我真的看不到任何有用的信息。是否可能是由非托管代码引起的,这就是列出的大多数线程不可用的原因? 显然我的最大值。 ThreadPool.GetMaxThreads 中的线程数为 1023,但 perfmon 当前显示超过 2400 个当前逻辑线程.. 嗯,除非明确覆盖,否则它始终是 250 的倍数。几乎没有关系,2400 个线程当然已经超出了快乐点和真正的问题。拥有 1023 并不能让它变得更好。 MaxThreads 是每个逻辑处理器的。因此,如果您有 4 个带超线程的内核,则必须将其乘以 8 才能获得“真正的”最大值。

以上是关于当前逻辑线程增加/线程堆栈泄漏的主要内容,如果未能解决你的问题,请参考以下文章

Eclipse MAT 显示许多线程没有堆栈

线程池 execute() 的工作逻辑

线程池

Android Socket通讯 之 表情列表优化业务逻辑优化

Android Socket通讯 之 表情列表优化业务逻辑优化

Android Socket通讯 之 表情列表优化业务逻辑优化