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为主 ...
随机推荐
- 你真的会使用Github吗?
快捷键 r 快速引用 你可以选中别人的评论文字,然后按r,这些内容会以引用的形式被复制在文本框中: t:搜索文件 s:光标定位到搜索窗口 w:选择分支 g n Go to Notifications ...
- 记一次ios加急上架经历
https://developer.apple.com//contact/app-store/?topic=expedite app迭代版本上架之前一直好好的没报错,没crash,但是有一次加急上架, ...
- 什么是wsgi,uwsgi,uWSGI
WSGI: web服务器网关接口,是一套协议.用于接收用户请求将请求进行初次封装,然后将请求交给web框架 实现wsgi协议的模块: 1,wsgiref,本质就是编写一个socket服务端,用于接收用 ...
- JAVA数组与List相互转换
1.数组转成List 数组转成List可以用方法 :Arrays.asList,一起来了解一下 System.out.println(Arrays.asList(new String[] { &quo ...
- Sphinx将python代码注释生成文档
安装 使用pip进行安装: pip install sphinx 初始化 进入你代码所在的目录,输入: sphinx-quickstart 下图:PRD是代码所在目录,生成的文档保存目录设成doc ...
- 安卓 dex 通用脱壳技术研究(三)
/* 此为DexHunter实现的主要功能,进行内存dump,将class_def_items中dump出classdef和extra部分 */ void* DumpClass(void *p ...
- Mysql安装和基本使用
MySQL的介绍安装.启动 windows上制作服务 MySQL破解密码 MySQL中统一字符编码 MySQL MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Ora ...
- 了解Git的工作区和暂存区
Git有工作区,暂存区之分. 1.工作区 我们电脑上的某个被Git管理的文件夹,就是一个工作区. 比如说我的GitWorkText文件夹,如图: 2.版本库(Repository) 在工作区有一个隐藏 ...
- AI之路,第二篇:python数学知识2
第二篇:python数学知识2 线性代数 导入相应的模块: >>> import numpy as np (数值处理模块)>>> import scipy ...
- Python之路PythonNet,第三篇,网络3
pythonnet 网络3 udp 通信 recvfrom sendtofork 多进程并发threading 多线程并发socketserver 系统模块 套接字的属性 setsockopt g ...