Thread与ThreadPool的内存之战

Thread与ThreadPool使用的时候在内存里对象是如何分布的呢?
今天咱们就从内存堆的角度分析下二者。
先上小白鼠代码:html

复制代码
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,查看该程序的线程状况windows

复制代码
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手动让它们消亡)。sass

3,而后咱们继续看看内存堆上它们这些坏家伙如何分布:app

复制代码
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
复制代码
 
红色部分,31个Thread,对应着31个Context,每一个线程在windows底层都是一个内核对象和一个栈空间,内核对象存放一些线程的统计信息,好比计数器以及一个上下文,就是我上次执行到那里等。而栈空间则是用来存放线程参数等。
 

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.
复制代码
 
windbg已经很清楚的告诉咱们,
一个对象能够
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
复制代码

并且在FinalizeQueue里也有它的踪迹:

 
复制代码
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
复制代码
 
下面就来解释下什么才能够在FinalizeQueue里出现呢?答案就是有身份的人,颇有身份的人,享受特殊待遇的哦!
啥身份,就是自身实现拉析构函数。

啥待遇,就是GC两次才有可能把他们部分清理掉!为啥部分,是咱们不知道windows到底什么时候去把全部的清理掉(赖皮阿)
具体原理你们能够看.net框架去,我这里很少说。
 

说到此,也就找到咱们当初30个彪形大汉为啥赖着不走的缘由拉,是在0代的第一次GC时候,他们被放进FinalizeQueue,等着第二次GC他们部分才会从内存堆上消亡。
为证实咱们的观点,咱们能够修改程序为 :
post

复制代码
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修改成:this

复制代码
  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);
            }
        }
复制代码
 
再用windbg查看线程时则为:

复制代码
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,到此吧。。。

但愿本文能对你有所帮助,谢谢! spa

 

 

出处:https://www.cnblogs.com/hsapphire/archive/2011/03/09/1978600.html

相关文章
相关标签/搜索