ChatGPT解决这个技术问题 Extra ChatGPT

垃圾收集器如何在这里避免无限循环?

考虑以下 C# 程序,我在 codegolf 上提交了它作为创建不循环的循环的答案:

class P{
    static int x=0;
    ~P(){
        System.Console.WriteLine(++x);
        new P();
    }
    static void Main(){
        new P();
    }
}

这个程序在我的检查中看起来像一个无限循环,但它似乎运行了几千次迭代,然后程序成功终止而没有错误(没有抛出错误)。最终没有调用 P 的终结器是否违反规范?

显然这是愚蠢的代码,不应该出现,但我很好奇程序如何完成。

原始代码高尔夫帖子:: https://codegolf.stackexchange.com/questions/33196/loop-without-looping/33218#33218

我害怕运行这个。
未调用终结器是 certainly within the realm of valid behavior。我不知道为什么要运行几千次迭代,但我希望调用为零。
CLR 可以防止终结器线程永远无法完成其工作。它会在 2 秒后强制终止它。
因此,标题中您问题的真正答案是,它通过让无限循环运行 40 秒然后终止它来避免它。
从尝试来看,该程序似乎无论如何都会在 2 秒后杀死所有东西。实际上,如果您继续生成线程,它将持续更长的时间:)

E
Eric Scherrer

根据 CLR 第二版中的 Richter 通过 C# (是的,我需要更新):

第 478 页

对于(CLR 正在关闭)每个 Finalize 方法大约有两秒钟的返回时间。如果 Finalize 方法没有在两秒内返回,CLR 只会终止进程——不再调用 Finalize 方法。此外,如果调用所有对象的 Finalize 方法需要超过 40 秒,CLR 会再次终止该进程。

此外,正如 Servy 所提到的,它有自己的线程。


此代码中的每个 finalize 方法对每个对象都花费了不到 40 秒的时间。创建一个新对象然后有资格进行终结与当前终结器无关。
这实际上并不是完成工作的原因。关闭时清空的易碎队列也有超时。这就是该代码失败的原因,它不断向该队列添加新对象。
只是想一想,清空 freachable 队列与“另外,如果调用所有对象的 Finalize 方法需要超过 40 秒的时间,CLR 会再次终止进程”。
N
Nate Cook

终结器不在主线程中运行。终结器有自己的线程来运行代码,它不是一个让应用程序保持运行的前台线程。主线程立即有效地完成,此时终结器线程只是在进程被拆除之前运行尽可能多的次数。没有什么能让程序保持活力。


如果由于没有主线程处于活动状态而在程序应该退出后 40 秒后终结器尚未完成,它将被终止并且进程将终止。这些都是旧值,所以微软现在可能已经调整了实际数字甚至整个算法。硒blog.stephencleary.com/2009/08/finalizers-at-process-exit.html
@LasseV.Karlsen 是记录在案的语言行为,还是仅仅是 MS 如何选择将其终结器作为实现细节来实现?我期待后者。
我也期待后者。我见过的对这种行为的最官方引用是上面 Eric 在他的回答中发布的内容,来自 Jeffrey Richter 的 CLR via C# 一书。
W
Willem Van Onsem

垃圾收集器不是一个活动系统。它“有时”运行并且主要是按需运行(例如,当操作系统提供的所有页面都已满时)。

大多数垃圾收集器在子线程中以类似广度优先的方式运行。在大多数情况下,回收对象可能需要数小时。

唯一的问题发生在您想要终止程序时。然而,这并不是一个真正的问题。当您使用 kill 时,操作系统会礼貌地要求终止进程。但是,当进程保持活动状态时,可以使用 kill -9,操作系统会删除所有控制。

当我在交互式 csharp 环境中运行您的代码时,我得到了:

csharp>  

1
2

Unhandled Exception:
System.NotSupportedException: Stream does not support writing
  at System.IO.FileStream.Write (System.Byte[] array, Int32 offset, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.FlushBytes () [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.FlushCore () [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.Write (System.Char[] buffer, Int32 index, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.Char[] buffer, Int32 index, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.Char[] val) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.String val) [0x00000] in <filename unknown>:0 
  at System.IO.TextWriter.Write (Int32 value) [0x00000] in <filename unknown>:0 
  at System.IO.TextWriter.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at System.IO.SynchronizedWriter.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at System.Console.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at P.Finalize () [0x00000] in <filename unknown>:0

因此,您的程序崩溃,因为 stdout 被环境终止阻止。

删除 Console.WriteLine 并终止程序时。五秒钟后程序终止(换句话说,垃圾收集器放弃并简单地释放所有内存而不考虑终结器)。


令人着迷的是交互式 csharp 因完全不同的原因而爆炸。原始程序片段没有控制台写入行,我很好奇它是否也会终止。
@MichaelB:我也对此进行了测试(请参阅下面的评论)。它等待五秒钟然后终止。我猜第一个 P 实例的终结器只是超时了。