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

相關文章
相關標籤/搜索