我来自 C++ 背景,我已经使用 C# 工作了大约一年。像许多其他人一样,我对为什么确定性资源管理没有内置在语言中感到困惑。
该using
构造提供“确定性”资源管理并内置于 C# 语言中。请注意,“确定性”的意思是保证在块开始执行Dispose
之后的代码之前被调用。using
另请注意,这不是“确定性”一词的含义,但每个人似乎都在这种情况下以这种方式滥用它,这很糟糕。
在我偏向 C++ 的大脑中,使用带有确定性析构函数的引用计数智能指针似乎是垃圾收集器的一大进步,垃圾收集器需要你实现 IDisposable 并调用 dispose 来清理你的非内存资源。
垃圾收集器不需要您实现IDisposable
. 事实上,GC 完全没有注意到它。
诚然,我不是很聪明......所以我问这个纯粹是为了更好地理解为什么事情会这样。
跟踪垃圾收集是一种模拟无限内存机器的快速可靠的方法,将程序员从手动内存管理的负担中解放出来。这消除了几类错误(悬空指针、过早释放、双重释放、忘记释放)。
如果 C# 被修改为:
对象是引用计数的。当对象的引用计数变为零时,会在对象上确定性地调用资源清理方法,
考虑一个在两个线程之间共享的对象。线程竞相将引用计数减为零。一个线程将赢得比赛,另一个负责清理。那是不确定的。认为引用计数本质上是确定性的信念是一个神话。
另一个常见的神话是引用计数在程序中最早的可能点释放对象。它没有。递减总是被推迟,通常到作用域的末尾。这使对象的存活时间超过了必要的时间,留下了所谓的“浮动垃圾”。请注意,特别是,一些跟踪垃圾收集器可以并且确实比基于范围的引用计数实现更早地回收对象。
然后将该对象标记为垃圾回收。垃圾收集发生在未来某个不确定的时间,此时内存被回收。在这种情况下,您不必实现 IDisposable 或记得调用 Dispose。
无论如何,您不必IDisposable
为垃圾收集的对象实现,所以这是无益的。
如果您有非内存资源要释放,您只需实现资源清理功能。
为什么这是个坏主意?
天真的引用计数非常慢并且会泄漏周期。例如, C++ 中的 Boostshared_ptr
比 OCaml 的跟踪 GC 慢 10 倍。在多线程程序(几乎所有现代程序)存在的情况下,即使是基于范围的简单引用计数也是不确定的。
这会破坏垃圾收集器的目的吗?
一点也不,不。事实上,这是一个坏主意,它是在 1960 年代发明的,并在接下来的 54 年中进行了激烈的学术研究,得出的结论是引用计数在一般情况下很糟糕。
实施这样的事情是否可行?
绝对地。早期的原型 .NET 和 JVM 使用引用计数。他们还发现它很糟糕并放弃了它以支持跟踪 GC。
编辑:从到目前为止的评论来看,这是一个坏主意,因为
没有引用计数的 GC 更快
是的。请注意,您可以通过延迟计数器递增和递减来更快地进行引用计数,但这会牺牲您非常渴望的确定性,并且它仍然比使用今天的堆大小跟踪 GC 慢。然而,引用计数越来越快,所以在未来某个时候,当堆变得非常大时,也许我们会开始在生产自动化内存管理解决方案中使用 RC。
处理对象图中的循环问题
试验删除是一种专门设计用于检测和收集参考计数系统中的循环的算法。但是,它是缓慢且不确定的。
我认为第一个是有效的,但第二个很容易使用弱引用来处理。
将弱引用称为“简单”是希望战胜现实的胜利。他们是一场噩梦。它们不仅不可预测且难以架构,而且会污染 API。
速度优化是否超过了您的缺点:
可能无法及时释放非内存资源
不及时using
释放非内存资源?
可能过早释放非内存资源如果您的资源清理机制是确定性的并且内置于语言中,则可以消除这些可能性。
该using
结构是确定性的并内置于语言中。
I think the question you really want to ask is why doesn't IDisposable
use reference counting. My response is anecdotal: I've been using garbage collected languages for 18 years and I have never needed to resort to reference counting. Consequently, I much prefer simpler APIs that aren't polluted with incidental complexity like weak references.