【发布时间】:2015-08-05 11:45:13
【问题描述】:
假设我们的程序有一个中心点(Document 类的一个实例),这里引用了各种信息。 现在我们有两个线程。两个线程都可以访问我们的“文档”,并且“文档”包含对“参数”(一个包含某种信息的对象)的引用。因此,如果我们有对“document”的引用,我们可以使用“document.params”来获取我们的 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。
【问题讨论】:
-
可能发生的最糟糕的情况是线程 1 下一次仍会读取 old
document.params,即使您同时从线程 2 更改了它。这就是为什么您要在每次访问共享字段时使用锁或内存屏障。或者完全避免使用共享字段:D -
刚刚更新了更新文档的params字段的锁。线程 1 中丢失的锁是有意的,在线程 1 看到线程 2 所做的更改之前可能会有延迟。
-
编写在调试时保证有错误的程序几乎没有意义。
-
只要您对最终的一致性感到满意,它可能是安全的。不过,请确保您不依赖于该锁之外的操作顺序(例如,设置一个标志以及赋值或其他东西)。从理论上讲,线程 1 可能永远看到更新的值,但在实际代码中这可能不是问题。不过,在 C#/JIT/CLR 的未来版本中,这种事情可能会发生。
-
@Hans Passant:您究竟在哪里看到了错误?它正在工作。但这可能是偶然的,而不是正确的。
标签: c# multithreading reference garbage-collection