c#垃圾收集器,线程和编译器/抖动优化

本文关键字:编译器 抖动 优化 线程 收集器 | 更新日期: 2023-09-27 18:06:24

假设我们的程序有一个中心点(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更新了文档。params引用,内存中没有指向旧"params"块的引用,因为线程1中的引用仅在CPU寄存器中。如果此时垃圾收集器启动,它怎么能看到旧的"Params"块仍在使用中呢?

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

c#垃圾收集器,线程和编译器/抖动优化

是否有可能,抖动可能优化线程1的代码,使tempParams不是一个内存地址,而是一个CPU寄存器?

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

另一个问题是:抖动可能会优化掉tempParams变量并使用document.params。直接a/b/c。

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

只是把下面的一些注释带入这个答案中——关于JIT"优化"代码是否有效的一些讨论,使得它看起来改变了局部变量的值。例如,MSDN上的这篇文章肯定表明它是有效的。很久以前,我看到过类似的东西,并在博客上写过。我有99%的信心和别人(可能是Joe Duffy)讨论过ECMA-335的有效阅读介绍是否有效,他们的印象是否定的。然而,我找不到任何明确的文件,ECMA-335在这件事上至少不清楚。

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