C# 垃圾收集器、线程和 Compiler/Jitter 优化

C# Garbage Collector, Threading and Compiler/Jitter optimization

假设我们的程序有一个中心点(Document class 的一个实例),其中引用了各种信息。 现在我们有两个线程。两个线程都可以访问我们的 "document" 并且 "document" 包含对 "params" 的引用(一个包含某种信息的对象)。因此,如果我们有对 "document" 的引用,我们可以使用 "document.params" 来获取我们的参数对象。

线程 1 执行以下操作:

Params tempParams = document.params; // get a local reference to documents.params
int a = tempParams.a; // read data from params
// thread 1 (this thread) gets interrupted by thread 2
int b = tempParams.b; // read data from params
int c = tempParams.c; // read data from params

线程 2 执行以下操作:

Params newParams = new Params();
... // fill newParams with new parameters
lock(obj) {
    document.params = newParams; // update params in document
}

因此 "params" 的内容永远不会更改,但如果需要更改,则会生成一个新副本并将引用 "document.params" 更新为新的 Params 块,这是一个原子操作。

现在最大的问题是:

抖动是否有可能优化线程 1 的代码,使 tempParams 不是内存地址而是 CPU 寄存器?如果线程 2 更新 document.params 引用,则内存中没有指向旧 "Params" 块的引用,因为线程 1 中的引用仅在 CPU 寄存器中。如果垃圾收集器就在这一刻启动,它怎么能看到旧的 "Params" 块仍在使用中?

另一个问题是:抖动是否会优化掉 tempParams 变量并直接使用 document.params.a/b/c。在这种情况下,线程 1 会看到 Params 对象的交换,这不是预期的。使用 tempParams 应确保线程 1 从复制引用时 document.params 中的同一 Params 对象访问 a/b/c。

Is it possible that the jitter might optimize the code of thread 1 that way that tempParams is not a memory address but a CPU register?

我怀疑这是可能的 - 但这不会阻止垃圾收集器将其视为对引用的使用。如果是这样,那将是一个 GC 错误。

The other question would be: might it happen that the jitter optimizes away the tempParams variable and uses document.params.a/b/c directly.

那将是一个 JIT 错误,IMO。不能保证线程 1 会看到对 document.params 的更改(因此在不同的情况下仍然需要考虑风险),但考虑到它 已经 将引用复制到本地变量 (tempParams) 并且该变量永远不会更改其值,所有通过 tempParams 的访问都将 寻址同一个对象。 (没有 tempParams.a 从一个对象读取但 tempParams.b 从另一个对象读取的风险。)

只是为了将下面的一些评论带入这个答案 - 有一些关于 JIT 对 "optimize" 代码是否有效的讨论,这样它似乎会改变局部变量的值。 This MSDN article certainly suggests it would be valid, for example. I saw something similar and blogged about it a long time ago。我 99% 确定我和某人(可能是 Joe Duffy)讨论了有效阅读介绍是否对 ECMA-335 有效,他们的印象是事实并非如此。但是,我找不到任何明确的文档,而且 ECMA-335 至少 不清楚这个问题。

ECMA-335 (CLI) 规范肯定比 MS 已经实施了一段时间的 CLR 2.0 模型更宽松,但我不认为 [=32] =] 不严。如果你不能依赖于与变化隔离的局部变量,那么很难编写 任何 有效代码,IMO。