为什么在 C# 中没有引用计数 + 垃圾收集?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/867114/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-05 04:49:01  来源:igfitidea点击:

Why no Reference Counting + Garbage Collection in C#?

c#garbage-collectionreference-counting

提问by Skrymsli

I come from a C++ background and I've been working with C# for about a year. Like many others I'm flummoxed as to why deterministic resource management is not built-in to the language. Instead of deterministic destructors we have the dispose pattern. People start to wonderwhether spreading the IDisposable cancer through their code is worth the effort.

我来自 C++ 背景并且我已经使用 C# 大约一年了。像许多其他人一样,我对为什么确定性资源管理没有内置到语言中感到困惑。我们有处置模式,而不是确定性析构函数。人们开始怀疑通过他们的代码传播 IDisposable 癌症是否值得付出努力。

In my C++-biased brain it seems like using reference-counted smart pointers with deterministic destructors is a major step up from a garbage collector that requires you to implement IDisposable and call dispose to clean up your non-memory resources. Admittedly, I'm not very smart... so I'm asking this purely from a desire to better understand why things are the way they are.

在我偏向 C++ 的大脑中,似乎使用带有确定性析构函数的引用计数智能指针是垃圾收集器的一大进步,垃圾收集器要求您实现 IDisposable 并调用 dispose 来清理您的非内存资源。不可否认,我不是很聪明……所以我问这个纯粹是为了更好地理解事情为什么会这样。

What if C# were modified such that:

如果 C# 被修改为:

Objects are reference counted. When an object's reference count goes to zero, a resource cleanup method is called deterministically on the object, then the object is marked for garbage collection. Garbage collection occurs at some non-deterministic time in the future at which point memory is reclaimed. In this scenario you don't have to implement IDisposable or remember to call Dispose. You just implement the resource cleanup function if you have non-memory resources to release.

对象是引用计数的。当对象的引用计数变为零时,将在该对象上确定性地调用资源清理方法,然后将该对象标记为进行垃圾回收。垃圾收集发生在未来某个不确定的时间,此时内存将被回收。在这种情况下,您不必实现 IDisposable 或记住调用 Dispose。如果您有非内存资源要释放,您只需实现资源清理功能。

  • Why is that a bad idea?
  • Would that defeat the purpose of the garbage collector?
  • Would it be feasible to implement such a thing?
  • 为什么这是个坏主意?
  • 这会破坏垃圾收集器的目的吗?
  • 实现这样的事情是否可行?

EDIT: From the comments so far, this is a bad idea because

编辑:从目前的评论来看,这是一个坏主意,因为

  1. GC is faster without reference counting
  2. problem of dealing with cycles in the object graph
  1. GC 在没有引用计数的情况下更快
  2. 处理对象图中的循环问题

I think number one is valid, but number two is easy to deal with using weak references.

我认为第一个是有效的,但第二个很容易使用弱引用处理。

So does the speed optimization outweigh the cons that you:

那么速度优化是否超过了您的缺点:

  1. may not free a non-memory resource in a timely manner
  2. might free a non-memory resource too soon
  1. 可能无法及时释放非内存资源
  2. 可能会过早释放非内存资源

If your resource cleanup mechanism is deterministic and built-in to the language you can eliminate those possibilities.

如果您的资源清理机制是确定性的并且内置于语言中,您就可以消除这些可能性。

采纳答案by Lucas

Brad Abrams posted an e-mail from Brian Harrywritten during development of the .Net framework. It details many of the reasons reference counting was not used, even when one of the early priorities was to keep semantic equivalence with VB6, which uses reference counting. It looks into possibilities such as having some types ref counted and not others (IRefCounted!), or having specific instances ref counted, and why none of these solutions were deemed acceptable.

Brad Abrams 发布了一封来自 Brian Harry在 .Net 框架开发期间撰写的电子邮件。它详细说明了许多未使用引用计数的原因,即使早期优先事项之一是与使用引用计数的 VB6 保持语义等效。它研究了一些可能性,例如对某些类型的 ref 进行计数而不对其他类型进行计数 ( IRefCounted!),或者对特定实例 ref 进行计数,以及为什么这些解决方案都不被认为是可接受的。

Because [the issue of resource management and deterministic finalization] is such a sensitive topic I am going to try to be as precise and complete in my explanation as I can. I apologize for the length of the mail. The first 90% of this mail is trying to convince you that the problem really is hard. In that last part, I'll talk about things we are trying to do but you need the first part to understand why we are looking at these options.

...

We initially started with the assumption that the solution would take the form of automatic ref counting(so the programmer couldn't forget) plus some other stuff to detect and handle cycles automatically. ...we ultimately concluded that this was not going to work in the general case.

...

In summary:

  • We feel that it is very important to solve the cycle problemwithout forcing programmers to understand, track down and design around these complex data structure problems.
  • We want to make sure we have a high performance (both speed and working set) system and our analysis shows that using reference counting for every single object in the system will not allow us to achieve this goal.
  • For a variety of reasons, including composition and casting issues, there is no simple transparent solution to having just those objects that need it be ref counted.
  • We chose not to select a solution that provides deterministic finalization for a single language/context because it inhibits interopwith other languages and causes bifurcation of class libraries by creating language specific versions.

因为 [资源管理和确定性最终确定的问题] 是一个如此敏感的话题,我将在我的解释中尽可能准确和完整。对于邮件的长度,我深表歉意。这封邮件的前 90% 试图让您相信这个问题真的很难。在最后一部分,我将讨论我们正在尝试做的事情,但您需要在第一部分了解我们为什么要考虑这些选项。

...

我们最初假设解决方案将采用自动引用计数的形式(因此程序员不会忘记)加上一些其他东西来自动检测和处理循环。...我们最终得出结论,这在一般情况下是行不通的。

...

总之:

  • 我们觉得不强迫程序员理解、追踪和围绕这些复杂的数据结构问题进行设计的情况下,解决循环问题是非常重要的。
  • 我们希望确保我们拥有一个高性能(速度和工作集)系统,我们的分析表明,对系统中的每个对象都使用引用计数将无法实现这一目标
  • 由于各种原因,包括组合和铸造问题,没有简单透明的解决方案来让那些需要它被引用计数的对象
  • 我们选择不选择为单一语言/上下文提供确定性终结的解决方案,因为它会抑制与其他语言的互操作,并通过创建特定于语言的版本导致类库分叉。

回答by Gishu

The garbage collector does not require you to write a Dispose method for everyclass/type that you define. You only define one when you need to explicitly do something to cleanup ; when you have explicitly allocated native resources. Most of the time, the GC just reclaims memory even if you only do something like new() up an object.

垃圾收集器不需要您为定义的每个类/类型编写 Dispose 方法。当你需要明确地做一些清理时,你只定义一个;当您明确分配本机资源时。大多数情况下,即使您只对对象执行 new() 之类的操作,GC 也会回收内存。

The GC does reference counting - however it does it in a different way by finding which objects are 'reachable' (Ref Count > 0) every time it does a collection... it just doesn't do it in a integer counter way. . Unreachable objects are collected (Ref Count = 0). This way the runtime doesn't have to do housekeeping/updating tables everytime an object is assigned or released... should be faster.

GC 会进行引用计数——但是它以不同的方式通过每次收集时查找哪些对象是“可到达的”(Ref Count > 0)来进行......它只是不是以整数计数器的方式进行。. 收集到无法访问的对象 ( Ref Count = 0)。这样,每次分配或释放对象时,运行时都不必进行内务管理/更新表......应该更快。

The only major difference between C++ (deterministic) and C# (non-deterministic) is when the object would be cleaned up. You can't predict the exact moment an object would be collected in C#.

C++(确定性)和 C#(非确定性)之间的唯一主要区别是何时清理对象。您无法预测在 C# 中收集对象的确切时刻。

Umpteenth plug: I'd recommend reading Jeffrey Richter's standup chapter on the GC in CLR via C#in case you're really interested in how the GC works.

无数插件:如果您真的对 GC 的工作方式感兴趣,我建议您阅读 Jeffrey Richter 关于通过 C# 使用 CLR 中的 GC 的独立章节。

回答by user8032

Reference counting was tried in C#. I believe, the folks that released Rotor (a reference implementation of CLR for which the source was made available) did reference counting-based GC just to see how it would compare to the generational one. The result was surprising -- the "stock" GC was so much faster, it was not even funny. I don't remember exactly where I heard this, I think it was one of the Hanselmuntes podcasts. If you want to see C++ get basically crushed in performance comparison with C# -- google Raymond Chen's chinese dictionary app. He did a C++ version, and then Rico Mariani did a C# one. I think it took Raymond 6 iterations to finally beat the C# version, but by that time he had to drop all the nice object orientednes of C++, and get down to the win32 API level. The entire thing turned into a performance hack. C# program, at the same time, was optimized only once, and in the end still looked like a decent OO project

在 C# 中尝试了引用计数。我相信,发布 Rotor(提供源代码的 CLR 的参考实现)的人做了基于引用计数的 GC,只是为了看看它与分代 GC 相比如何。结果令人惊讶——“股票”GC 速度快得多,甚至都不好笑。我不记得我是在哪里听到的,我想这是 Hanselmuntes 播客之一。如果您想看到 C++ 在与 C# 的性能比较中基本上被压垮了——谷歌 Raymond Chen 的中文词典应用程序。他做了一个 C++ 版本,然后 Rico Mariani 做了一个 C# 版本。我认为 Raymond 进行了 6 次迭代才最终击败 C# 版本,但到那时他不得不放弃 C++ 的所有面向对象,并降到 win32 API 级别。整个事情变成了一个性能黑客。

回答by Unknown

I know something about garbage collection. Here is a short summary because a full explanation is beyond the bounds of this question.

我对垃圾收集有所了解。这是一个简短的总结,因为完整的解释超出了这个问题的范围。

.NET uses a copying and compacting generational garbage collector. This is more advanced than reference counting and has the benefit of being able to collect objects that refer to themselves either directly, or through a chain.

.NET 使用复制和压缩分代垃圾收集器。这比引用计数更先进,并且具有能够收集直接或通过链引用自身的对象的好处。

Reference counting will not collect cycles. Reference counting also has a lower throughput (slower overall) but with the benefit of faster pauses (maximal pauses are smaller) than a tracing collector.

引用计数不会收集周期。引用计数也具有较低的吞吐量(总体上较慢),但与跟踪收集器相比,具有更快的暂停(最大暂停更小)的好处。

回答by mlarsen

The object implemeting IDisposable must also implement a finalizer called by the GC when the user doesn't explicit call Dispose - see IDisposable.Dispose at MSDN.

当用户没有显式调用 Dispose 时,实现 IDisposable 的对象还必须实现由 GC 调用的终结器 - 请参阅MSDN 上的 IDisposable.Dispose

The whole point of IDisposable is that the GC is running at some non-deterministic time and you implement IDisposable because you hold a valuable resource and wants to free it at a deterministic time.

IDisposable 的全部意义在于 GC 在某个不确定的时间运行,而您实施 IDisposable 是因为您拥有宝贵的资源并希望在确定的时间释放它。

So your proposal would change nothing in terms of IDisposable.

所以你的提议不会改变 IDisposable 的任何内容。

Edit:

编辑:

Sorry. Didn't read your proposal correctly. :-(

对不起。没有正确阅读您的建议。:-(

Wikipedia has a simple explanation of the shortcomings of References counted GC

维基百科对References counted GC缺点有一个简单的解释

回答by Brian Rasmussen

There's a lot of issues in play here. First of all you need to distinguish between freeing managed memory and clean-up of other resources. The former can be really fast whereas the later may be very slow. In .NET the two are separated, which allows for faster clean-up of managed memory. This also implies, that you should only implement Dispose/Finalizer when you have something beyond managed memory to clean up.

这里有很多问题。首先,您需要区分释放托管内存和清理其他资源。前者可能非常快,而后者可能非常慢。在 .NET 中,两者是分开的,这样可以更快地清理托管内存。这也意味着,只有在需要清理托管内存之外的内容时,才应该实现 Dispose/Finalizer。

The .NET employs a mark and sweep technique where it traverses the heap looking for roots to objects. Rooted instances survive the garbage collection. Everything else can be cleaned by just reclaiming the memory. The GC has to compact memory every now and then, but apart from that reclaiming memory is a simple pointer operation even when reclaiming multiple instances. Compare this with multiple calls to destructors in C++.

.NET 采用标记和清除技术,它遍历堆寻找对象的根。有根的实例在垃圾收集中幸存下来。其他一切都可以通过回收内存来清理。GC 必须时不时地压缩内存,但除了回收内存之外,即使回收多个实例也是一个简单的指针操作。将此与 C++ 中对析构函数的多次调用进行比较。

回答by SO User

Reference count

引用计数

The costs of using reference counts are twofold: First, every object requires the special reference count field. Typically, this means an extra word of storage must be allocated in each object. Second, every time one reference is assigned to another, the reference counts must be adjusted. This increases significantly the time taken by assignment statements.

使用引用计数的成本是双重的:首先,每个对象都需要特殊的引用计数字段。通常,这意味着必须在每个对象中分配一个额外的存储字。其次,每次将一个引用分配给另一个引用时,必须调整引用计数。这显着增加了赋值语句所花费的时间。

Garbage Collection in .NET

.NET 中的垃圾收集

C# does not use reference counting of the objects. Instead it maintains a graph of the object references from the stack and navigates from the root to cover up all the referenced objects. All the referenced objects in the graph are compacted in the heap to that a contiguous memory is available for future objects. Memory for all the unreferenced objects who do not need to be finalized is reclaimed. Those that are unreferenced but have finalizers to be executed on them are moved to a separate queue called the f-reachable queue where the garbage collector calls their finalizers in the background.

C# 不使用对象的引用计数。相反,它维护来自堆栈的对象引用的图形,并从根导航以覆盖所有引用的对象。图中所有引用的对象都在堆中压缩,以便为将来的对象提供连续的内存。所有不需要最终确定的未引用对象的内存被回收。那些未被引用但有终结器要在其上执行的对象被移动到一个单独的队列,称为 f-reachable 队列,垃圾收集器在后台调用它们的终结器。

In addition to the above GC uses the concept of generations for a more efficient garbage collection. It is based on the following concepts 1. It is faster to compact the memory for a portion of the managed heap than for the entire managed heap 2. Newer objects will have shorter lifetimes and older objects will have longer lifetimes 3. Newer objects tend to be related to each other and accessed by the application around the same time

除了上述 GC 使用代的概念来进行更有效的垃圾收集。它基于以下概念 1. 压缩一部分托管堆的内存比压缩整个托管堆的内存更快 2. 较新的对象将具有较短的生命周期,而较旧的对象将具有较长的生命周期 3. 较新的对象倾向于彼此相关并由应用程序大约在同一时间访问

The managed heap is divided into three generations: 0, 1, and 2. The new objects are stored in gen 0. Objects that are not reclaimed by a cycle of GC are promoted to the next gen. So if newer objects which are in gen 0 survive GC cycle 1, then they are promoted to gen 1. Those among these that survive GC cycle 2 are promoted to gen 2. Because the garbage collector supports only three generations, objects in generation 2 that survive a collection remain in generation 2 until they are determined to be unreachable in a future collection.

托管堆分为三代:0、1、2。新的对象存储在第0代,没有被GC循环回收的对象被提升到下一代。因此,如果第 0 代中较新的对象在 GC 周期 1 中幸存下来,那么它们将被提升到 1 代。其中那些在 GC 周期 2 中幸存下来的对象将被提升到 2 代。因为垃圾收集器仅支持三代,因此第 2 代中的对象将在一个集合中存活,保留在第 2 代,直到它们被确定在未来的集合中不可访问。

The garbage collector performs a collection when generation 0 is full and memory for new object needs to be allocated. If a collection of generation 0 does not reclaim enough memory, the garbage collector can perform a collection of generation 1, then generation 0. If this does not reclaim enough memory, the garbage collector can perform a collection of generations 2, 1, and 0.

当第 0 代已满并且需要为新对象分配内存时,垃圾收集器执行收集。如果第 0 代的收集没有回收足够的内存,垃圾收集器可以执行第 1 代的收集,然后是第 0 代。如果这没有回收足够的内存,垃圾收集器可以执行第 2、1 和 0 代的收集.

Thus GC is more efficient than reference count.

因此 GC 比引用计数更有效。

回答by Luke Quinane

There is a difference between C++ style smart pointer reference counting and reference counting garbage collection. I've also talked about the differences on my blogbut here is a quick summary:

C++ 风格的智能指针引用计数和引用计数垃圾收集之间存在差异。我也在我的博客上谈到了差异,但这里有一个快速总结:

C++ Style Reference Counting:

C++ 风格的引用计数:

  • Unbounded cost on decrement:if the root of a large data structure is decremented to zero there is an unbounded cost to free all the data.

  • Manual cycle collection:to prevent cyclic data structures from leaking memory the programmer must manually break any potential structures by replacing part of the cycle with a weak smart pointer. This is another source of potential defects.

  • 无界递减成本:如果大型数据结构的根递减为零,则释放所有数据的成本无界。

  • 手动循环收集:为了防止循环数据结构泄漏内存,程序员必须通过用弱智能指针替换循环的一部分来手动破坏任何潜在的结构。这是潜在缺陷的另一个来源。

Reference Counting Garbage Collection

引用计数垃圾收集

  • Deferred RC:Changes to an object reference count are ignored for stack and register references. Instead when a GC is triggered these objects are retained by collecting a root set. Changes to the reference count can be deferred and processed in batches. This results in higher throughput.

  • Coalescing:using a write barrier it is possible to coalescechanges to the reference count. This makes it possible to ignore most changes to an objects reference count improving RC performance for frequently mutated references.

  • Cycle Detection:for a complete GC implementation a cycle detector must also be used. However it is possible to perform cycle detection in incremental fashion, which in turn means bounded GC time.

  • 延迟 RC:对于堆栈和寄存器引用,对对象引用计数的更改将被忽略。相反,当触发 GC 时,这些对象通过收集根集来保留。对引用计数的更改可以推迟并批量处理。这导致更高的吞吐量

  • 合并:使用写屏障可以合并对引用计数的更改。这使得可以忽略对对象引用计数的大多数更改,从而提高频繁变异引用的 RC 性能。

  • 循环检测:对于完整的 GC 实施,还必须使用循环检测器。然而,可以以增量方式执行循环检测,这又意味着有界 GC 时间。

Basically it is possible to implement a high performance RC based garbage collector for runtimes such as Java's JVM and the .net CLR runtime.

基本上,可以为运行时(例如 Java 的 JVM 和 .net CLR 运行时)实现基于高性能 RC 的垃圾收集器。

I think tracing collectors are partly in use for historical reasons: many of the recent improvements in reference counting came after both the JVM and .net runtime were released. Research work also takes time to transition into production projects.

我认为使用跟踪收集器的部分原因是历史原因:最近引用计数方面的许多改进都是在 JVM 和 .net 运行时发布之后出现的。研究工作也需要时间来过渡到生产项目。

Deterministic Resource Disposal

确定性资源处置

This is pretty much a separate issue. The .net runtime makes this possible using the IDisposable interface, example below. I also like Gishu'sanswer.

这几乎是一个单独的问题。.net 运行时使用 IDisposable 接口使这成为可能,示例如下。我也喜欢Gishu 的回答。



@Skrymsli, this is the purpose of the "using" keyword. E.g.:

@Skrymsli,这是“使用”关键字的目的。例如:

public abstract class BaseCriticalResource : IDiposable {
    ~ BaseCriticalResource () {
        Dispose(false);
    }

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this); // No need to call finalizer now
    }

    protected virtual void Dispose(bool disposing) { }
}

Then to add a class with a critical resource:

然后添加一个具有关键资源的类:

public class ComFileCritical : BaseCriticalResource {

    private IntPtr nativeResource;

    protected override Dispose(bool disposing) {
        // free native resources if there are any.
        if (nativeResource != IntPtr.Zero) {
            ComCallToFreeUnmangedPointer(nativeResource);
            nativeResource = IntPtr.Zero;
        }
    }
}

Then to use it is as simple as:

然后使用它很简单:

using (ComFileCritical fileResource = new ComFileCritical()) {
    // Some actions on fileResource
}

// fileResource's critical resources freed at this point

See also implementing IDisposable correctly.

另请参阅正确实施 IDisposable

回答by Viktor S?derqvist

Deterministic non-memory resource management is part of the language, however it is not done with destructors.

确定性非内存资源管理是语言的一部分,但它不是通过析构函数完成的。

Your opinion is common among people coming from a C++ background, attempting to use the RAIIdesign pattern. In C++ the only way you can guarrantee that some code will run in the end of a scope, even if an exeption is thrown, is to allocate an object on the stack and put the clean-up code in the destructor.

您的意见在具有 C++ 背景并尝试使用RAII设计模式的人中很常见。在 C++ 中,即使抛出异常,您也可以保证某些代码将在作用域末尾运行的唯一方法是在堆栈上分配一个对象并将清理代码放入析构函数中。

In other languages (C#, Java, Python, Ruby, Erlang, ...) you can use try-finally (or try-catch-finally) instead to ensure that the clean-up code will always run.

在其他语言(C#、Java、Python、Ruby、Erlang 等)中,您可以使用 try-finally(或 try-catch-finally)来确保清理代码始终运行。

// Initialize some resource.
try {
    // Use the resource.
}
finally {
    // Clean-up.
    // This code will always run, whether there was an exception or not.
}

I C#, you can also use the usingconstruct:

IC#,您还可以使用using构造:

using (Foo foo = new Foo()) {
    // Do something with foo.
}
// foo.Dispose() will be called afterwards, even if there
// was an exception.

Thus, for a C++-programmer, it might help to think about "running clean-up code" and "freeing memory" as two separate things. Put your clean-up code in a finally block and leave to the GC to take care of the memory.

因此,对于 C++ 程序员,将“运行清理代码”和“释放内存”视为两个独立的事情可能会有所帮助。将清理代码放在 finally 块中,让 GC 处理内存。

回答by Jon Harrop

I come from a C++ background and I've been working with C# for about a year. Like many others I'm flummoxed as to why deterministic resource management is not built-in to the language.

我来自 C++ 背景并且我已经使用 C# 大约一年了。像许多其他人一样,我对为什么确定性资源管理没有内置到语言中感到困惑。

The usingconstruct provides "deterministic" resource management and is built into the C# language. Note that by "deterministic" I mean Disposeis guaranteed to have been called before the code after the usingblock starts executing. Note also that this is not what the word "deterministic" means but everyone seems to abuse it in this context in that way, which sucks.

using构造提供“确定性”资源管理并内置于 C# 语言中。请注意,“确定性”我的意思Dispose是保证在using块开始执行之后的代码之前已经被调用。还要注意,这不是“确定性”这个词的意思,但每个人似乎都在这种情况下以这种方式滥用它,这很糟糕。

In my C++-biased brain it seems like using reference-counted smart pointers with deterministic destructors is a major step up from a garbage collector that requires you to implement IDisposable and call dispose to clean up your non-memory resources.

在我偏向 C++ 的大脑中,似乎使用带有确定性析构函数的引用计数智能指针是垃圾收集器的一大进步,垃圾收集器要求您实现 IDisposable 并调用 dispose 来清理您的非内存资源。

The garbage collector does not require you to implement IDisposable. Indeed, the GC is completely oblivious to it.

垃圾收集器不需要您实现IDisposable. 事实上,GC 完全没有注意到它。

Admittedly, I'm not very smart... so I'm asking this purely from a desire to better understand why things are the way they are.

不可否认,我不是很聪明……所以我问这个纯粹是为了更好地理解事情为什么会这样。

Tracing garbage collection is a fast and reliable way to emulate an infinite memory machine, freeing the programmer from the burden of manual memory management. This eliminated several classes of bugs (dangling pointers, free too soon, double free, forgot to free).

跟踪垃圾回收是一种模拟无限内存机器的快速可靠的方法,可以将程序员从手动内存管理的负担中解放出来。这消除了几类错误(悬空指针,释放太快,双重释放,忘记释放)。

What if C# were modified such that:

Objects are reference counted. When an object's reference count goes to zero, a resource cleanup method is called deterministically on the object,

如果 C# 被修改为:

对象是引用计数的。当对象的引用计数变为零时,将在该对象上确定性地调用资源清理方法,

Consider an object shared between two threads. The threads race to decrement the reference count to zero. One thread will win the race and the other will be responsible for cleanup. That is non-deterministic. The belief that reference counting is inherently deterministic is a myth.

考虑一个在两个线程之间共享的对象。线程争先恐后地将引用计数减少到零。一个线程将赢得比赛,另一个将负责清理。那是不确定的。认为引用计数本质上是确定性的是一个神话。

Another common myth is that reference counting frees objects at the earliest possible point in the program. It doesn't. Decrements are always deferred, usually to the end of scope. This keeps objects alive for longer than necessary leaving what is called "floating garbage" lying around. Note that, in particular, some tracing garbage collectors can and do recycle objects earlier than scope-based reference counting implementations.

另一个常见的误区是引用计数在程序中尽可能早的时候释放对象。它没有。递减总是推迟,通常到作用域的末尾。这使物体存活的时间超过了必要的时间,留下了所谓的“漂浮垃圾”。请注意,特别是一些跟踪垃圾收集器可以并且确实比基于范围的引用计数实现更早地回收对象。

then the object is marked for garbage collection. Garbage collection occurs at some non-deterministic time in the future at which point memory is reclaimed. In this scenario you don't have to implement IDisposable or remember to call Dispose.

然后对象被标记为垃圾收集。垃圾收集发生在未来某个不确定的时间,此时内存将被回收。在这种情况下,您不必实现 IDisposable 或记住调用 Dispose。

You don't have to implement IDisposablefor garbage collected objects anyway, so that is a non-benefit.

IDisposable无论如何,您不必为垃圾收集对象实现,因此这是无益的。

You just implement the resource cleanup function if you have non-memory resources to release.

Why is that a bad idea?

如果您有非内存资源要释放,您只需实现资源清理功能。

为什么这是个坏主意?

Naive reference counting is very slow and leaks cycles. For example, Boost's shared_ptrin C++ is up to 10x slower than OCaml's tracing GC. Even naive scope-based reference counting is non-deterministic in the presence of multithreaded programs (which is almost all modern programs).

朴素的引用计数非常慢并且会泄漏循环。例如,C++ 中的Boostshared_ptr比 OCaml 的跟踪 GC 慢 10 倍。在多线程程序(几乎所有现代程序)存在的情况下,即使是基于范围的简单引用计数也是不确定的。

Would that defeat the purpose of the garbage collector?

这会破坏垃圾收集器的目的吗?

Not at all, no. In fact it is a bad idea that was invented in the 1960s and subjected to intense academic study for the next 54 years concluding that reference counting sucks in the general case.

完全没有,没有。事实上,这是一个坏主意,它是在 1960 年代发明的,并在接下来的 54 年中进行了大量的学术研究,得出的结论是引用计数在一般情况下很糟糕。

Would it be feasible to implement such a thing?

实现这样的事情是否可行?

Absolutely. Early prototype .NET and JVM used reference counting. They also found it sucked and dropped it in favor of tracing GC.

绝对地。早期原型 .NET 和 JVM 使用引用计数。他们还发现它很糟糕并放弃了它以支持跟踪 GC。

EDIT: From the comments so far, this is a bad idea because

GC is faster without reference counting

编辑:从目前的评论来看,这是一个坏主意,因为

GC 在没有引用计数的情况下更快

Yes. Note that you can make reference counting much faster by deferring counter increments and decrements but that sacrifices the determinism that you crave so very much and it is still slower than tracing GC with today's heap sizes. However, reference counting is asymptotically faster so at some point in the future when heaps get really big maybe we will start using RC in production automated memory management solutions.

是的。请注意,您可以通过推迟计数器的增量和减量来使引用计数更快,但这会牺牲您非常渴望的确定性,并且它仍然比使用今天的堆大小跟踪 GC 慢。然而,引用计数渐近地更快,所以在未来的某个时候,当堆变得非常大时,我们可能会开始在生产自动化内存管理解决方案中使用 RC。

problem of dealing with cycles in the object graph

处理对象图中的循环问题

Trial deletion is an algorithm specifically designed to detect and collect cycles in reference counted systems. However, it is slow and non-deterministic.

试验删除是一种专门设计用于检测和收集参考计数系统中循环的算法。但是,它很慢且不确定。

I think number one is valid, but number two is easy to deal with using weak references.

我认为第一个是有效的,但第二个很容易使用弱引用处理。

Calling weak references "easy" is a triumph of hope over reality. They are a nightmare. Not only are they unpredictable and difficult to architect but they pollute APIs.

将弱引用称为“容易”是对现实的希望的胜利。他们是一场噩梦。它们不仅不可预测且难以构建,而且会污染 API。

So does the speed optimization outweigh the cons that you:

may not free a non-memory resource in a timely manner

那么速度优化是否超过了您的缺点:

可能无法及时释放非内存资源

Doesn't usingfree non-memory resource in a timely manner?

不及时using释放非内存资源?

might free a non-memory resource too soon If your resource cleanup mechanism is deterministic and built-in to the language you can eliminate those possibilities.

可能会过早释放非内存资源如果您的资源清理机制是确定性的并且内置于语言中,您可以消除这些可能性。

The usingconstruct is deterministic and built into the language.

using构造是确定性的,并内置于语言中。

I think the question you really want to ask is why doesn't IDisposableuse 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.

我认为您真正想问的问题是为什么不IDisposable使用引用计数。我的回答是轶事:我已经使用垃圾收集语言 18 年了,而且我从来不需要求助于引用计数。因此,我更喜欢更简单的 API,它们不会被诸如弱引用之类的偶然复杂性所污染。