首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >线程的垃圾回收

线程的垃圾回收
EN

Stack Overflow用户
提问于 2011-01-18 14:08:21
回答 4查看 4.4K关注 0票数 7

我是否需要保护Thread对象不受垃圾回收器的影响?那么包含线程运行的函数的对象又如何呢?

考虑一下这个简单的服务器:

代码语言:javascript
复制
class Server{
    readonly TcpClient client;

    public Server(TcpClient client){this.client = client;}

    public void Serve(){
        var stream = client.GetStream();
        var writer = new StreamWriter(stream);
        var reader = new StreamReader(stream);

        writer.AutoFlush = true;
        writer.WriteLine("Hello");

        while(true){
            var input = reader.ReadLine();
            if(input.Trim() == "Goodbye")
                break;
            writer.WriteLine(input.ToUpper());
        }

        client.Close();
    }
}
static void Main(string[] args){
    var listener = new TcpListener(IPAddress.Any, int.Parse(args[0]));
    listener.Start();

    while(true){
        var client = listener.AcceptTcpClient();
        var server = new Server(client);
        var thread = new Thread(server.Serve);
        thread.Start();
    }
}

我是否应该将我的线程对象包装在某种静态收集中,以防止它们被垃圾收集器清除?

假设,如果Thread对象本身保持活动状态,那么Server对象也会活动,因为线程持有对委托的引用,而委托持有对目标对象的引用。也可能是线程对象本身被收集,但实际的线程继续运行。现在可以收集Server对象了。然后,如果它试图访问它的字段,会发生什么呢?

垃圾回收有时会让我头晕目眩。我很高兴我通常不需要去想它。

考虑到这里的潜在陷阱,我相信垃圾收集器足够聪明,不会在线程本身仍在执行时收集线程对象,但我找不到任何文档这么说。Reflector在这里没有什么帮助,因为Thread类的大部分都是在MethodImplOptions.InternalCall函数中实现的,这并不奇怪。而且我不愿意在我的旧的SSCLI副本中寻找答案(这既是因为它很痛苦,也因为它不是一个确定的答案)。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2011-01-18 14:14:25

这很简单。真正的执行线程不是thread对象。程序是在真正的Windows线程中执行的,不管你的.NET垃圾收集器对你的线程对象做了什么,这些线程都会保持活动状态。所以它对你来说是安全的;如果你只想让程序继续运行,你不需要关心Thread对象。

还要注意,您的线程在运行时不会被收集,因为它们实际上属于应用程序的“根”。(根-垃圾收集器如何知道什么是活的。)

更多细节:托管线程对象可以通过Thread.CurrentThread访问-这类似于全局静态变量,但这些变量不会被收集。正如我之前所写的:任何启动的托管线程现在执行任何代码(甚至在.NET之外),都不会丢失它的thread对象,因为它牢固地连接到“根”。

票数 6
EN

Stack Overflow用户

发布于 2011-01-18 14:31:15

只要你的线程正在执行,它就不会被收集。

票数 2
EN

Stack Overflow用户

发布于 2011-01-18 17:05:25

thread constructor中的start参数(在本例中是server.Serve )是一个您已经知道的委托。

假设,如果线程对象本身保持活动状态,则服务器对象也将活动,因为线程持有对委托的引用,该委托持有对目标对象的引用

这是Jon Skeet所写的C# in Depth对委托目标生命周期的描述

值得注意的是,如果委托实例本身不能被收集,则委托实例将阻止其目标被垃圾收集。

所以,是的,只要var thread在作用域中,就不会收集server。但是,如果在调用thread.start之前var thread超出了作用域(并且没有其他引用),那么它是可以被收集的。

现在最大的问题是。调用Thread.Start(),并且在server.Serve完成之前线程已经超出作用域,GC就可以收集线程了。

与其到处钻研,不如直接测试一下

代码语言:javascript
复制
class Program
{
    static void Main(string[] args)
    {
        test();
        GC.Collect(2);
        GC.WaitForPendingFinalizers();
        Console.WriteLine("test is over");
    }

    static void test()
    {
        var thread = new Thread(() =>  {
            long i = 0;

            while (true)
            {
                i++;   
                Console.WriteLine("test {0} {1} {2} ", Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId i);
                Thread.Sleep(1000); //this is a demo so its okay
            }
        });
        thread.Name = "MyThread";
        thread.Start();

    }

}

这是输出。(添加threadID后)

代码语言:javascript
复制
test MyThread 3 1
test is over
test MyThread 3 2
test MyThread 3 3
test MyThread 3 4
test MyThread 3 5

所以我调用了一个创建线程并启动它的方法。该方法结束,因此var thread超出了作用域。但是,即使我引发了一个GC,并且线程变量超出了作用域,线程仍会继续运行。

因此,只要在线程超出作用域之前启动它,一切都会正常工作

更新以澄清GC.Collect

强制立即对所有代进行垃圾回收。

任何可以收集到的东西都会被收集。这并不像评论所说的那样“仅仅是一种意图的表达”。(如果是,就没有理由谨慎地调用它)然而,我添加了参数"2“以确保它是通过gen2实现的gen0。

但是,您关于终结器的观点值得注意。所以我添加了GC.WaitForPendingFinalizers

...挂起当前线程,直到正在处理终结器队列的线程清空该队列。

这意味着所有需要处理的终结器确实都已经处理过了。

示例的要点是,只要启动线程,它就会一直运行到中止或结束,GC不会因为var thread超出作用域而以某种方式中止线程。

作为一个旁观者, Al Kepp是正确的,事实上,一旦你启动线程,System.Threading.Thread就是根的。您可以通过使用SOS扩展来了解这一点。

例如:

代码语言:javascript
复制
!do 0113bf40
Name:        System.Threading.Thread
MethodTable: 79b9ffcc
EEClass:     798d8ed8
Size:        48(0x30) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
79b88a28  4000720        4 ....Contexts.Context  0 instance aaef947d00000000 m_Context
79b9b468  4000721        8 ....ExecutionContext  0 instance aaef947d00000000 m_ExecutionContext
79b9f9ac  4000722        c        System.String  0 instance 0113bf00 m_Name
79b9fe80  4000723       10      System.Delegate  0 instance 0113bf84 m_Delegate
79ba63a4  4000724       14 ...ation.CultureInfo  0 instance aaef947d00000000 m_CurrentCulture
79ba63a4  4000725       18 ...ation.CultureInfo  0 instance aaef947d00000000 m_CurrentUICulture
79b9f5e8  4000726       1c        System.Object  0 instance aaef947d00000000 m_ThreadStartArg
79b9aa2c  4000727       20        System.IntPtr  1 instance 001D9238 DONT_USE_InternalThread
79ba2978  4000728       24         System.Int32  1 instance        2 m_Priority
79ba2978  4000729       28         System.Int32  1 instance        3 m_ManagedThreadId
79b8b71c  400072a      18c ...LocalDataStoreMgr  0   shared   static s_LocalDataStoreMgr
    >> Domain:Value  0017fd80:NotInit  <<
79b8e2d8  400072b        c ...alDataStoreHolder  0   shared TLstatic s_LocalDataStore

这是名为MyThread的线程吗

代码语言:javascript
复制
!do -nofields 0113bf00
Name:        System.String
MethodTable: 79b9f9ac
EEClass:     798d8bb0
Size:        30(0x1e) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:      MyThread

是啊,是这样的。

它的根基是什么?

代码语言:javascript
复制
!GCRoot 0113bf40
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 7708 OSTHread 1e1c
Scan Thread 4572 OSTHread 11dc
Scan Thread 9876 OSTHread 2694
ESP:101f7ec:Root:  0113bf40(System.Threading.Thread)
ESP:101f7f4:Root:  0113bf40(System.Threading.Thread)
DOMAIN(0017FD80):HANDLE(Strong):9b11cc:Root:  0113bf40(System.Threading.Thread)

正如Production Debugging for .NET Framework Applications所说,它是根。

请注意!GCRoot是在启动后运行的。在它启动之前,它没有手柄(强)。

如果输出包含句柄(强),则找到强引用。这意味着对象是根对象,不能被垃圾回收。其他参考类型可以在附录中找到。

有关托管线程如何映射到OS线程(以及如何使用SOS扩展进行确认)的更多信息,请参见Yun Jin的this article

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/4720991

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档