繁体   English   中英

并发环境中的乱序加载

[英]Out of order loading in concurrent environment

下面是Joe Duffy的书(Windows上的Concurrent Programming)的片段,后面是该段所涉及的代码段。 这段代码意味着在并发环境(由许多线程使用)中工作,其中此LazyInit<T>类用于创建仅在真正需要在(在类型T中使用)值的情况下初始化的am对象。码。

如果有人能够详细说明无序负载到负载可能会产生问题的逐步方案,我将不胜感激。 也就是说,如果每个线程的加载顺序首先加载字段然后加载引用而不是我们期望的方式,那么使用该类并将引用及其字段分配给变量的两个或多个线程怎么可能是个问题呢?它是(首先加载引用,然后加载通过引用获得的字段值)?

我知道这种情况很少发生(失败是因为无序加载)。 事实上,我可以看到一个线程可以在不知道引用值(指针?)是什么的情况下首先错误地读取字段的值,但如果发生这种情况,那么该线程将自行纠正(就好像它不在并发环境)如果发现过早负载值不正确; 在这种情况下,装载最终会成功。 换句话说,另一个线程的存在怎么可能使加载线程不“意识到”加载线程中的无序加载是无效的?

我希望我设法传达问题,因为我真的看到它。

片段:

由于上面提到的所有处理器,除了.NET内存模型,在某些情况下允许加载到加载重新排序,m_value的加载可能在加载对象的字段后移动。 效果类似,将m_value标记为volatile会阻止它。 将对象的字段标记为易失性是不必要的,因为读取值是获取栅栏并防止后续加载之前移动,无论它们是否是易失性的。 这对某些人来说可能看起来很荒谬:在引用对象本身之前如何读取字段? 这似乎违反了数据依赖性,但它没有:一些较新的处理器(如IA64)采用价值推测并将提前执行​​负载。 如果处理器恰好猜测引用和字段的正确值,就像在写入引用之前那样,推测性读取可能会退出并产生问题。 这种重新排序是非常罕见的,可能永远不会在实践中发生,但它仍然是一个问题。

代码示例:

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;
        }
    }
}

一些较新的处理器(如IA64)采用价值推测并将提前执行​​负载。 如果处理器恰好猜测引用和字段的正确值,就像在写入引用之前那样,推测性读取可能会退出并产生问题。

这基本上对应于以下源转换:

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)实际上是一个语句,通过该语句将prdeiction(基于值,参见每个m_value的上一次)放入测试中。 我理解在顺序编程(非并发)中,测试必须始终因上次看到的任何值而失败,但在并发编程中,测试(预测)可能成功,并且处理器的执行流程将在尝试打印无效值时发生(i..e,null someFieldValuePrefetched)

我的问题是,这种错误预测怎么可能只能在并发编程中成功,而不能在顺序,非并发编程中成功。 并且与该问题相关,在并发编程中,当处理器接受该错误预测时,m_value的可能值是什么(即,必须为null,非null)?

推测是否this.m_value不依赖于线程,而是取决于this.m_value是否与上次执行时的值相同。 如果它很少变化,那么推测往往会成功。

首先,我必须说,我非常感谢你对此事的帮助。 为了磨练我的理解,这是我如何看待它,如果我错了,请纠正我。

如果线程T1要执行错误的推测加载路径,则将执行以下代码行:

Thread T1 line 1: int someFieldValuePrefetched = lastValueSeen.SomeField; //prefetch speculatively
Thread T1 line 2: if (this.m_value == lastValueSeen) {
 //speculation succeeded (accelerated case). The speculated read is used
 Thread T1 line 3: Console.WriteLine(someFieldValuePrefetched);
}
else {
 //speculation failed (slow case). The speculated read is discarded.
…..
….
}

另一方面,线程T2将需要执行以下代码行。

Thread T2 line 1: old = m_value;
Thread T2 line 2: m_value = new object();
Thread T2 line 3: old.SomeField = 1;

我的第一个问题是:当执行“Thread T1 line 1”时,this.m_value的含义是什么? 我想它在执行“线程T2第2行”之前等于旧的m_value,对吗? 否则,推测分支将不会选择加速路径,这导致我询问线程T2是否也必须以乱序方式执行其代码行。 也就是说,它执行“线程T2线1”,“线程T2线3”,“线程T2线2”而不是“线程T2线1”,“线程T2线2”,“线程T2线3”? 如果是这样,那么我相信volatile关键字也会阻止线程T2以乱序的方式执行代码,对吗?

我可以看到线程T1的“线程T1线2”在线程T2的“线程T2线1”和“线程T2线3”之后和“线程T2线2”之前执行,然后线程T1中的SomeField将为1正如你所指出的那样,这是没有意义的,因为当SomeField变为1时,为m_value分配一个新值,SomeField的值为0

如果它仍然是实际的,请考虑以下代码,它来自Joe Duffy的CPOW:

    MyObject mo = new LazyInit<MyObject>(someFactory).Value;
    int f = mo.field;
    if (f == 0)
    {
        //Do Something...
        Console.WriteLine(f);
    }

下面的文字也来自书中“如果初始读取mo.field到变量f之间的时间段以及随后在Console.WriteLine中使用f的时间足够长,编译器可能会认为它会更有效率重读mo.field两次....如果保持该值会产生寄存器压力,编译器可能会决定这一点,导致堆栈空间使用效率降低:

    ...
    if (mo.field == 0)
    {
        ////Do Something...
        Console.WriteLine(mo.field);
    }

所以,我认为这可能是退役裁判的一个很好的例子。 到mo.field的后续使用时,mo 的推测读取可以退出并创建一个空引用异常,这肯定是个问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM