如果有人能够详细说明无序负载到负载可能会产生问题的逐步方案,我将不胜感激.也就是说,如果每个线程的加载顺序首先加载字段然后加载引用而不是我们期望的方式,那么使用该类并将引用及其字段分配给变量的两个或多个线程怎么可能是个问题呢?它是(首先加载参考,然后加载通过参考获得的字段的值)?
我知道这种情况很少发生(失败是因为无序加载).事实上,我可以看到一个线程可以在不知道引用值(指针?)是什么的情况下首先错误地读取字段的值,但如果发生这种情况,那么该线程将自行纠正(就好像它不在并发环境)如果发现过早的负载值不正确;在这种情况下,装载最终会成功.换句话说,另一个线程的存在如何使加载线程不“意识到”加载线程中的无序加载是无效的?
我希望我设法传达问题,因为我真的看到它.
片段:
Because all of the processors mentioned above,in addition to the .NET memory model,allow load-to-load reordering in some circumstances,the load of m_value could move after the load of the object’s fields. The effect would be similar and marking m_value as volatile prevents it. Marking the object’s fields as volatile is not necessary because the read of the value is an acquire fence and prevents the subsequent loads from moving before,no matter whether they are volatile or not. This might seem ridiculous to some: how could a field be read before a reference to the object itself? This appears to violate data dependence,but it doesn’t: some newer processors (like IA64) employ value speculation and will execute loads ahead of time. If the processor happens to guess the correct value of the reference and field as it was before the reference was written,the speculative read could retire and create a problem. This kind of reordering is quite rare and may never happen in practice,but nevertheless it is a problem.
代码示例:
public class LazyInitOnlyOnceRef<T> where T : class { private volatile T m_value; private object m_sync = new object(); private Func<T> m_factory; public LazyInitOnlyOnceRef(Func<T> factory) { m_factory = factory; } public T Value { get { if (m_value == null) { lock (m_sync) { if (m_value == null) m_value = m_factory(); } } return m_value; } } }
解决方法
Some newer processors (like IA64) employ value speculation and will execute loads ahead of time. If the processor happens to guess the correct value of the reference and field as it was before the reference was written,the speculative read could retire and create a problem.
这基本上对应于以下源转换:
var obj = this.m_value; Console.WriteLine(obj.SomeField);
变
[ThreadStatic] static object lastValueSeen = null; //processor-cache //... int someFieldValuePrefetched = lastValueSeen.SomeField; //prefetch speculatively if (this.m_value == lastValueSeen) { //speculation succeeded (accelerated case). The speculated read is used Console.WriteLine(someFieldValuePrefetched); } else { //speculation Failed (slow case). The speculated read is discarded. var obj = this.m_value; lastValueSeen = obj; //remember last value Console.WriteLine(obj.SomeField); }
处理器尝试预测加热缓存所需的下一个内存地址.
实质上,您不能再依赖数据依赖性,因为可以在指向包含对象的指针之前加载字段.
你问:
if (this.m_value == lastValueSeen) is really the statement by which
prdeiction (based on the value see last time per m_value) is put to
the test. I understand that in sequential programming (non
concurrent),the test must always fail for whatever value was last
seen,but in concurrent programming that test (the prediction) could
succeed and the processor’s flow of execution will ensue in an attempt
to print invalid value (i..e,null someFieldValuePrefetched)My question is how could it be that this false prediction could
succeed only in concurrent programming but not in sequential,
non-concurrent programming. And in connection to that question,in
concurrent programming when this false prediction is accepted by the
processor,what are the possible value of m_value (i.e.,must it be
null,non null) ?
推测是否成功不依赖于线程,而是取决于this.m_value是否与上次执行时的值相同.如果它很少变化,那么推测往往会成功.