为什么CLR需要一个专用线程来调用finalize方法



这是一篇msdn文章的摘录。文章可在这里找到

有一个特殊的运行时线程专门用于调用Finalize方法。当可再生队列为空时(通常是这种情况),该线程休眠。但是当条目出现时,该线程唤醒,从队列中删除每个条目,并调用每个对象的Finalize方法。因此,不应该在Finalize方法中执行任何对执行该代码的线程做任何假设的代码。例如,避免在Finalize方法中访问线程本地存储。

这是我以前问过的一个问题,我被否决了同样的说法,没有专门的线程用于Finalizable对象,我的信息来源是错误的。如果这篇文章解释的是真的,那么我想知道为什么CLR需要一个特殊的线程来调用Finalize方法?

关于终结器所做的不需要单独的线程,但是考虑一下:

  • 当GC在第0代和第1代上运行时,托管线程被挂起
  • 并发或后台收集可以在托管线程恢复后的第2代上运行(MSDN: Fundamentals of Garbage collection中有很多关于这方面的信息)

然而,一个看似天真的解决方案是这样做:

  • 暂停所有线程(或者可能让触发GC的线程运行,只是让它做GC代替)
  • 执行垃圾收集,包括这需要做的一切
  • 恢复所有线程

这可能很容易实现。GC操作可以自由访问与堆相关的所有数据结构,并且可以自由地做任何它想做的事情,因为没有其他线程正在运行访问这些东西。

但是,这将使程序不时地有"相当长的"停顿,并且在大多数应用程序中是不可接受的。

因此,处理GC的团队选择了尽可能短时间挂起的解决方案,仅在需要时(第0代和第1代收集)挂起,同时在收集第2代时保持应用程序运行。这个更难。注意,这是我的假设,我没有访问实际的设计文档或任何可靠的信息。假设基于官方文档。

回到终结器线程。这些对象已经"死"了,并被指定为收集对象,所以与其采用上述设计决策,为什么还要让主线程处理这些对象呢?由于GC已经在后台做了一些事情,因此以同样的方式处理死对象的终结似乎是一个不错的决定。

您可以通过利用并发性来提高性能,特别是在具有多个CPU核心的现代处理器架构上。垃圾收集非常有用,但它可能会对性能产生一些影响,因为当垃圾收集器重新安排内存中对象的布局时,应用程序将无法执行任何操作。然而,任何结束都可以在后台执行,这就是为什么它在一个单独的线程上完成,以提高性能。

  1. 终结器是你的最后一道防线——如果你的资源没有被正确地处理,那么在将来的某个地方CLR自己会尝试完成清理。这个过程是完全不确定的——它不是RAII。它不是设计来处理一些事件处理程序解挂钩或类似的东西-它应该清理非托管资源,你的实例使用,并没有正确处置(托管资源将被处置,即使没有直接终结器,至少如果没有任何循环依赖)。所以没有必要在你的工作线程上执行。
  2. 总的垃圾收集对你的程序来说是不确定的和不引人注目的——它发生在CLR决定做它的时候,而你不知道它,也不能真正影响它(好吧,有
  3. 未管理的资源可能相当大,应该尽快处理。
  4. 即使需要在同一线程上执行终结器,那么它如何实际实现?为了在任意时刻在线程上执行代码,线程必须提供某种SyncronizationContext,而不是每个线程都能提供。因此,每个线程都必须提供这样的SyncronizationContext——这不是最好的主意。

考虑到所有这些观察,专用线程的终结看起来是一个相当合理和有效的解决方案。

最新更新