.net 线程的垃圾收集
我是否需要保护.net 线程的垃圾收集,.net,multithreading,.net,Multithreading,我是否需要保护线程对象不受垃圾收集器的影响?那么包含线程运行的函数的对象呢 考虑这个简单的服务器: class Server{ readonly TcpClient client; public Server(TcpClient client){this.client = client;} public void Serve(){ var stream = client.GetStream(); var writer = new Str
线程对象不受垃圾收集器的影响?那么包含线程运行的函数的对象呢
考虑这个简单的服务器:
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();
}
}
我是否应该将线程对象包装在某种静态收集中,以防止它们被垃圾收集器清除
假设,如果线程
对象本身保持活动状态,那么服务器
对象将保持活动状态,因为线程持有对委托的引用,委托持有对目标对象的引用。或者可能收集了线程对象本身,但实际线程继续运行。现在,服务器
对象可以收集了。如果它试图访问其字段,会发生什么情况
垃圾收集有时让我头晕目眩。我很高兴我通常不用去想它
考虑到这里可能存在的问题,我认为垃圾收集器足够聪明,在线程本身仍在执行时不会收集线程对象,但我找不到任何文档这样说。Reflector在这里没有什么帮助,因为很多线程
类都是在MethodImplOptions.InternalCall
函数中实现的。我不想翻阅我那本过时的SSCLI来寻找答案(既因为这很痛苦,也因为它不是一个确定的答案)。这很简单。真正的执行线程不是线程对象。该程序在真实的Windows线程中执行,无论.NET垃圾收集器对线程对象做了什么,这些线程都保持活动状态。所以对你来说是安全的;如果只想让程序继续运行,就不需要关心线程对象
还要注意,线程在运行时不会被收集,因为它们实际上属于应用程序“根”。(根-垃圾收集器如何知道什么是活动的。)
更多详细信息:托管线程对象可通过Thread.CurrentThread
访问-这类似于全局静态变量,不会收集这些变量。正如我之前所写的:任何已启动并正在执行任何代码(即使在.NET之外)的托管线程都不会丢失其线程对象,因为它与“根”紧密相连。只要线程正在执行,它就不会被收集。start参数(在您的情况下是server.service)在中,有一位您已经认识的代表
假设线程对象
自身保持活动状态,然后服务器保持活动状态
对象将处于活动状态,因为线程
保存对委托的引用
其中包含对目标的引用
反对
这就是乔恩·斯基特(Jon Skeet)在《深度》中对代表目标生命的描述
值得注意的是
实例将阻止其目标
正在进行垃圾收集,如果
委托实例本身不能为空
收集
因此,只要var线程
在范围内,就不会收集yesserver
。如果var thread
在调用thread.start之前超出范围(并且没有其他引用),则可以收集它
现在是一个大问题。调用Thread.Start(),并且在server.service
完成之前线程已超出范围。GC是否可以收集线程
与其四处挖掘,不如测试一下
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后)
所以我调用一个方法来创建一个线程并启动它。方法结束时,var线程
超出范围。但是,即使我已经引入了一个GC,并且线程变量超出了范围,线程仍会继续运行
因此,只要在线程超出范围之前启动线程,一切都将按预期工作
更新
澄清
强制立即对所有代进行垃圾收集
任何可以收集到的东西都将被销毁。这并不像评论所说的那样“仅仅是一种意图的表达”。(如果是这样的话,就没有理由警告不要叫它)但是我添加了参数“2”,以确保它是gen0到gen2
然而,你关于终结器的观点值得注意。所以我加了一个
。。。挂起当前线程,直到处理终结器队列的线程清空该队列
这意味着需要处理的任何终结器实际上都已处理
示例的要点是,只要启动线程,它就会一直运行,直到它被中止或完成,并且GC不会因为var线程
超出范围而以某种方式中止线程
作为旁白
Al Kepp的观点是正确的,即一旦启动线程,System.Threading.thread就会被根化。您可以通过使用SOS扩展来了解这一点
e、 g
是的,是的
这是为了什么
!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)
正如《圣经》所说,它是根
注意!GCRoot在启动后运行。在启动之前,它没有手柄(坚固)
如果输出包含句柄(强),
找到了有力的参考。这
表示对象是根对象,并且
无法进行垃圾收集。其他
可以在中找到引用类型
附录
有关托管线程如何映射到操作系统线程(以及如何使用SOS扩展进行确认)的更多信息,请参阅Yun Jin 尽管我担心在涉及多线程或GC(更不用说两者)时,用测试代码证明问题很困难,但我自己还是做了一个简单的小测试。我认为调查结果令人满意地解决了这个问题
我越是阅读这里贴出的答案,越是思考,它似乎越能归结为一个具体的问题:
线程
对象的终结器在我的线程退出之前运行时会造成多大的破坏
显然,GC本身会
!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
!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
!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)
Thread t;
t = new Thread(o=>Console.WriteLine(o == Thread.CurrentThread));
t.Start(t);