Thread与ThreadPool的内存之战
Thread与ThreadPool使用的时候在内存里对象是如何分布的呢?
今天我们就从内存堆的角度分析下两者。
先上小白鼠代码:
static void Main(string[] args) { for (int i = 0; i < 30; i++) { Thread t = new Thread(new ThreadStart(ThreadProc)); t.Name = "Overred_" + i; t.Start(); } Console.Read(); } static void ThreadProc() { try { for (int i = 0; i < 10; i++) { Console.WriteLine("{0} Value:{1}",Thread.CurrentThread.Name,i); } } catch (Exception ex) { Console.WriteLine(ex.Message); } }
以上代码非常简单,就是循环启动30个线程去执行同一个方法ThreadProc(),然后打印出结果。
现在提出问题1:当Main里的30个线程都把ThreadProc()方法执行完毕后,这些Threads是自动消亡还是被GC回收,还是变成DeadThread?
好,拿出我们的看家工具windbg,来debug一把。
首先启动我们的程序,然后打开windbg,然后F6,Attach我们的exe
1,加载mscorwks(.net 2.0或者以上)
0:003> .loadby sos mscorwks
2,查看该程序的线程情况
0:003> !Threads
*** ERROR: Symbol file could not be found. Defaulted to export symbols for
C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll -
PDB symbol for mscorwks.dll not loaded
ThreadCount: 32
UnstartedThread: 0
BackgroundThread: 1
PendingThread: 0
DeadThread: 30
Hosted Runtime: no
PreEmptive GC Alloc Lock
ID OSID ThreadOBJ State GC Context Domain Count APT Exception
0 1 25e4 00518858 a020 Enabled 013f878c:013f9fe8 00514818 1 MTA
2 2 24b8 00526f20 b220 Enabled 00000000:00000000 00514818 0 MTA (Finalizer)
XXXX 3 0 00533028 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 4 0 00536858 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 5 0 005385c8 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 6 0 005393d0 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 7 0 00534fd8 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 8 0 0053a5c0 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 9 0 0053b3c8 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX a 0 0053bfc0 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX b 0 0053eba8 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX c 0 00543370 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX d 0 00543b38 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX e 0 00544700 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX f 0 00544ec8 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 10 0 00545690 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 11 0 00545ee0 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 12 0 005466c0 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 13 0 00546a88 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 14 0 00546e50 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 15 0 00547218 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 16 0 005475e0 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 17 0 005479a8 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 18 0 00547d70 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 19 0 00548138 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 1a 0 00548500 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 1b 0 005488c8 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 1c 0 00548c90 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 1d 0 00549058 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 1e 0 00549420 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 1f 0 005497e8 9820 Enabled 00000000:00000000 00514818 0 Ukn
XXXX 20 0 00549bb0 9820 Enabled 00000000:00000000 00514818 0 Ukn
看红色加粗部分,我们总共有32个线程,而DeadThread为30个(其他2个为程序自身所有,其中一个BackgroundThread),先告诉你这30个死线程正式我们循环创建的线程,可以回答我提的第一个问题拉,没错,他们统统死拉,而且不会醒来,还占地方(不是永远占地方,待会我们用GC手动让它们消亡)。
3,然后我们继续看看内存堆上它们这些坏家伙如何分布:
0:003> !DumpHeap -type System.Threading -stat
total 155 objects
Statistics:
MT Count TotalSize Class Name
79108930 1 32 System.Threading.ContextCallback
790fe284 2 144 System.Threading.ThreadAbortException
79124b74 30 600 System.Threading.ThreadHelper
79104de8 31 1116 System.Threading.ExecutionContext
790fe704 31 1736 System.Threading.Thread
791249e8 60 1920 System.Threading.ThreadStart
Total 155 objects
4,我们来具体看下这些Thread们的MethodTable
0:003> !DumpHeap -MT 790fe704
Address MT Size
013c1708 790fe704 56
013c178c 790fe704 56
013c235c 790fe704 56
013c2474 790fe704 56
013c258c 790fe704 56
013c26a4 790fe704 56
013c27bc 790fe704 56
013c28d4 790fe704 56
013c29ec 790fe704 56
013c2b04 790fe704 56
013c2c1c 790fe704 56
013c2d34 790fe704 56
013c2e54 790fe704 56
013c2f74 790fe704 56
013c3094 790fe704 56
013c31b4 790fe704 56
013c32d4 790fe704 56
013c33f4 790fe704 56
013c3514 790fe704 56
013c3634 790fe704 56
013c3754 790fe704 56
013c3874 790fe704 56
013c3994 790fe704 56
013c3ab4 790fe704 56
013c3bd4 790fe704 56
013c3cf4 790fe704 56
013c3e14 790fe704 56
013c3f34 790fe704 56
013f8084 790fe704 56
013f81a4 790fe704 56
013f82c4 790fe704 56
total 31 objects
Statistics:
MT Count TotalSize Class Name
790fe704 31 1736 System.Threading.Thread
Total 31 objects
5,随便拿一个线程的Address来看看到底是谁占着我们的Thread而不让我们的GC回收掉
0:003> !GCRoot 013c3bd4 Note: Roots found on stacks may be false positives. Run "!help gcroot" for more info. Scan Thread 0 OSTHread 25e4 Scan Thread 2 OSTHread 24b8 DOMAIN(00514818):HANDLE(WeakSh):241298:Root:013c3bd4(System.Threading.Thread)
结果另我们很失望,他自己就是根,并没被其他任何对象所引用,什么情况下会出现此情况呢?我们先来看看对象在内存中分布的几种方式,我们只需在windbg里执行如下命令则知:
0:003> !Help gcroot
-------------------------------------------------------------------------------
!GCRoot [-nostacks] <Object address>
!GCRoot looks for references (or roots) to an object. These can exist in four
places:
1. On the stack
2. Within a GC Handle
3. In an object ready for finalization
4. As a member of an object found in 1, 2 or 3 above.
First, all stacks will be searched for roots, then handle tables, and finally
the freachable queue of the finalizer. Some caution about the stack roots:
!GCRoot doesn't attempt to determine if a stack root it encountered is valid
or is old (discarded) data. You would have to use !CLRStack and !U to
disassemble the frame that the local or argument value belongs to in order to
determine if it is still in use.
Because people often want to restrict the search to gc handles and freachable
objects, there is a -nostacks option.
一个对象可以
1,在栈上
2,在一个GCHandle里(可以执行!GCHandles命令查看)
3,在FinalizeQueue里
4,是一个对象的成员
难道对象就必定在以上的“四行”之中吗?答案是不一定,还有个Gchandleleaks,就是你在内存里看不到这个Handle,它已经leak。(这种也算在GCHandle里吧)。
回头我们接着说他自己没被其他任何对象所引用,自己就是个根,但是GC却不搭理它,为何?那就是他在GCHandle里,
0:003> !GCHandles
GC Handle Statistics:
Strong Handles: 14
Pinned Handles: 4
Async Pinned Handles: 0
Ref Count Handles: 0
Weak Long Handles: 0
Weak Short Handles: 31
Other Handles: 0
Statistics:
MT Count TotalSize Class Name
790fd0f0 1 12 System.Object
790fcc48 1 24 System.Reflection.Assembly
790feba4 1 28 System.SharedStatics
790fe17c 1 72 System.ExecutionEngineException
790fe0e0 1 72 System.StackOverflowException
790fe044 1 72 System.OutOfMemoryException
790fed00 1 100 System.AppDomain
79100a18 4 144 System.Security.PermissionSet
790fe284 2 144 System.Threading.ThreadAbortException
790fe704 32 1792 System.Threading.Thread
7912d8f8 4 8736 System.Object[]
Total 49 objects
0:003> !FinalizeQueue
SyncBlocks to be cleaned up: 0
MTA Interfaces to be released: 0
STA Interfaces to be released: 0
----------------------------------
generation 0 has 35 finalizable objects (00526658->005266e4)
generation 1 has 0 finalizable objects (00526658->00526658)
generation 2 has 0 finalizable objects (00526658->00526658)
Ready for finalization 0 objects (005266e4->005266e4)
Statistics:
MT Count TotalSize Class Name
791037c0 1 20 Microsoft.Win32.SafeHandles.SafeFileMappingHandle
79103764 1 20 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle
79101444 2 40 Microsoft.Win32.SafeHandles.SafeFileHandle
790fe704 31 1736 System.Threading.Thread
Total 35 objects
啥身份,就是自身实现拉析构函数。
具体原理大家可以看.net框架去,我这里不多说。
说到此,也就找到我们当初30个彪形大汉为啥赖着不走的原因拉,是在0代的第一次GC时候,他们被放进FinalizeQueue,等着第二次GC他们部分才会从内存堆上消亡。
为证明我们的观点,我们可以修改程序为 :
static void Main(string[] args) { for (int i = 0; i < 30; i++) { Thread t = new Thread(new ThreadStart(ThreadProc)); t.Name = "Overred_" + i; t.Start(); } GC.Collect(); GC.Collect(); Console.Read(); }
首先声明一点就是当我们调用一次GC.Collect();时,并不是执行一次垃圾收集,只是告诉系统我要强制进行垃圾收集,系统听到这个命令后乖不乖那就不一定拉。
当我们用Reflector查看mscorlib对Thread实现的使用也会发现他实现拉析构:
~Thread() { this.InternalFinalize(); }
来个虎头蛇尾吧,当我们把小白鼠程序使用ThreadPool修改为:
static void Main(string[] args)
{
for (int i = 0; i < 30; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
}
Console.Read();
}
static void ThreadProc(object o)
{
try
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine(" Value:{0}",i);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
0:006> !Threads
*** ERROR: Symbol file could not be found. Defaulted to export symbols
for C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll -
PDB symbol for mscorwks.dll not loaded
ThreadCount: 4
UnstartedThread: 0
BackgroundThread: 3
PendingThread: 0
DeadThread: 0
而FinalizeQueue则为:
0:006> !FinalizeQueue
SyncBlocks to be cleaned up: 0
MTA Interfaces to be released: 0
STA Interfaces to be released: 0
----------------------------------
generation 0 has 7 finalizable objects (00266658->00266674)
generation 1 has 0 finalizable objects (00266658->00266658)
generation 2 has 0 finalizable objects (00266658->00266658)
Ready for finalization 0 objects (00266674->00266674)
Statistics:
MT Count TotalSize Class Name
791037c0 1 20 Microsoft.Win32.SafeHandles.SafeFileMappingHandle
79103764 1 20 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle
79101444 2 40 Microsoft.Win32.SafeHandles.SafeFileHandle
790fe704 3 168 System.Threading.Thread
Total 7 objects
那现在又出现问题拉,既然ThreadPool这么好,那我们为啥还使用Thread呢?这个问题就是ThreadPool有个GetMaxThreads,可以通过GetMaxThreads(out int workerThreads, out int completionPortThreads);方法获取到,如果线程池满拉,则会死锁更严重!
另:ThreadPool都为后台线程。
究竟使用那个,根据情况而定,理解拉内在的东西,一切表象就简单拉。
OK,到此吧。。。
希望本文能对你有所帮助,谢谢!
出处:https://www.cnblogs.com/hsapphire/archive/2011/03/09/1978600.html
Thread与ThreadPool的内存之战的更多相关文章
- 线程(Thread,ThreadPool)、Task、Parallel
线程(Thread.ThreadPool) 线程的定义我想大家都有所了解,这里我就不再复述了.我这里主要介绍.NET Framework中的线程(Thread.ThreadPool). .NET Fr ...
- 【python】threadpool的内存占用问题
先说结论: 在使用多线程时,不要使用threadpool,应该使用threading, 尤其是数据量大的情况.因为threadpool会导致严重的内存占用问题! 对比threading和threadp ...
- Thread、ThreadPool、Task、Parallel、Async和Await基本用法、区别以及弊端
多线程的操作在程序中也是比较常见的,比如开启一个线程执行一些比较耗时的操作(IO操作),而主线程继续执行当前操作,不会造成主线程阻塞.线程又分为前台线程和后台线程,区别是:整个程序必须要运行完前台线程 ...
- C#异步和多线程以及Thread、ThreadPool、Task区别和使用方法
本文的目的是为了让大家了解什么是异步?什么是多线程?如何实现多线程?对于当前C#当中三种实现多线程的方法如何实现和使用?什么情景下选用哪一技术更好? 第一部分主要介绍在C#中异步(async/awai ...
- Thread、ThreadPool 和 Task
对 C# 开发者来说,不可不理解清楚 Thread.ThreadPool 和 Task 这三个概念.这也是面试频率很高的话题,在 StackOverflow 可以找到有很多不错的回答,我总结整理了一下 ...
- C#中Thread与ThreadPool的比较
最近同事在编写一个基于UPD RTP协议的通信软件,在处理接收Listen时,发现了一个问题到底是用Thread还是ThreadPool呢? 我看同事的问题比较有典型性,还是做以整理培训一下吧 Thr ...
- Thread and ThreadPool
C#中Thread与ThreadPool的比较 Thread类,一次使用一个线程,来创建和删除线程.这种方式建立和删除线程是很昂贵的(cpu密集型). Threadpool类 对于大多数的情况下是使用 ...
- 多线程异步编程示例和实践-Thread和ThreadPool
说到多线程异步编程,总会说起Thread.ThreadPool.Task.TPL这一系列的技术.总结整理了一版编程示例和实践,分享给大家. 先从Thread和ThreadPool说起: 1. 创建并启 ...
- NET 异步多线程,THREAD,THREADPOOL,TASK,PARALLEL
.NET 异步多线程,THREAD,THREADPOOL,TASK,PARALLEL,异常处理,线程取消 今天记录一下异步多线程的进阶历史,以及简单的使用方法 主要还是以Task,Parallel为主 ...
随机推荐
- 进程中的Manager(),实现多进程的数据共享与传递
__author__ = "Alex Li" from multiprocessing import Process, Managerimport osdef f(d, l): d ...
- 深入理解java虚拟机---虚拟机工具jstat(十七)
jstack---没什么用 jstack用于生成java虚拟机当前时刻的线程快照.线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因 ...
- SAL-9 获取所有部门当前manager的当前薪水情况,给出dept_no, emp_no以及salary,当前表示to_date='9999-01-01'
题目描述 获取所有部门当前manager的当前薪水情况,给出dept_no, emp_no以及salary,当前表示to_date='9999-01-01'CREATE TABLE `dept_man ...
- Centos7 LNMP 一键安装
首页: https://lnmp.org/ 安装包生成页: https://lnmp.org/auto.html
- 监控中的TP50
TP指标: TP50:指在一个时间段内(如5分钟),统计该方法每次调用所消耗的时间,并将这些时间按从小到大的顺序进行排序,取第50%的那个值作为TP50 值:配置此监控指标对应的报警阀值后,需要保证在 ...
- 【腾讯开源】前端预处理器语言图形编译工具 Koala使用指南
摘要:Koala是一款预处理器语言图形编译工具,支持Less.Sass.CoffeeScript.Compass框架的即时编译.无需手动输入命令去编译,后台监听文件是否有改变,如有修改会自动进行编译, ...
- WebClient和WebRequest获取html代码
HTML: <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.as ...
- <Spark><Programming><Key/Value Pairs><RDD>
Working with key/value Pairs Motivation Pair RDDs are a useful building block in many programs, as t ...
- javascript性能优化之Dom编程性能调优总结
1.最小化的Dom访问,在一次Dom访问做尽可能多的操作: 2.使用局部变量存放指向反复访问的元素节点的Dom引用,原则上js代码中不应该重复获取同一个元素节点,除非它在运行过程中发生改变: 3.对元 ...
- JAVA拼合数组方法
方法一: package org.ken.array; import java.lang.reflect.Array; import java.util.Arrays; public class Jo ...