C#可以写指令从finally块重新排序到try块吗?

时间:2017-04-05 13:33:37

标签: c# volatile memory-barriers

在一篇关于A scalable reader/writer scheme with optimistic retry的文章中,有一个代码示例:

using System;
using System.Threading;

public class OptimisticSynchronizer
{
    private volatile int m_version1;
    private volatile int m_version2;

    public void BeforeWrite() {
        ++m_version1;
    }

    public void AfterWrite() {
        ++m_version2;
    }

    public ReadMark GetReadMark() {
        return new ReadMark(this, m_version2);
    }

    public struct ReadMark
    {
        private OptimisticSynchronizer m_sync;
        private int m_version;

        internal ReadMark(OptimisticSynchronizer sync, int version) {
            m_sync = sync;
            m_version = version;
        }

        public bool IsValid {
            get { return m_sync.m_version1 == m_version; }
        }
    }

    public void DoWrite(Action writer) {
        BeforeWrite();
        try {
            writer(); // this is inlined, method call just for example
        } finally {
            AfterWrite();
        }
    }

    public T DoRead<T>(Func<T> reader) {
        T value = default(T);

        SpinWait sw = new SpinWait();
        while (true) {
            ReadMark mark = GetReadMark();

            value = reader();

            if (mark.IsValid) {
                break;
            }

            sw.SpinOnce();
        }

        return value;
    }
}

如果我m_version1m_version2 不易变,请使用以下代码:

public void DoWrite(Action writer) {
    Thread.MemoryBarrier(); // always there, acquiring write lock with Interlocked method
    Volatile.Write(ref m_version1, m_version1 + 1); // NB we are inside a writer lock, atomic increment is not needed
    try {
        writer();
    } finally {
        // is a barrier needed here to avoid the increment reordered with writer instructions?
        // Volatile.Write(ref m_version2, m_version2 + 1); // is this needed instead of the next line?
        m_version2 = m_version2 + 1; // NB we are inside a writer lock, atomic increment is not needed
        Thread.MemoryBarrier(); // always there, releasing write lock with Interlocked method
    }
}

m_version2 = m_version2 + 1的说明是否可以从finally重新排序到try块?作者在m_version2递增之前完成是很重要的。

逻辑finallytry后执行,但list of implicit memory barriers中未提及finally块。 如果来自finally的指令可以在来自try的指令之前移动,那将会非常令人困惑,但是在指令级别的CPU优化对我来说仍然是一个黑魔法。

我可以将Thread.MemoryBarrier();放在第m_version2 = m_version2 + 1行之前(或使用Volatile.Write),但问题是这是否真的需要?

示例中显示的MemoryBarrier是隐式的,由编写器锁的Interlocked方法生成,因此它们始终存在。危险在于读者可以在作者完成之前看到m_version2递增。

1 个答案:

答案 0 :(得分:1)

我没有找到任何会限制它的规格,所以我用ARM CPU设备检查了它(使用Xamarin ,必须在Core CLR上检查它...)<登记/> 一个线程正在执行此代码:

try
{
    person = new Person();
}
finally
{
    isFinallyExecuted = true;
}

第二个帖子正在等待isFinallyExecuted使用此代码true

while (!Volatile.Read(ref isFinallyExecuted))
    ;

然后第二个线程正在执行以下代码:

if (!person.IsInitialized())
{
    failCount++;
    Log.Error("m08pvv", $"Reordered from finally: {failCount}, ok: {okCount}");
}
else
{
    okCount++;
}

IsInitialized方法检查所有字段是否已正确设置,因此它会为部分构造的对象返回false

这就是我在日志中得到的:

  

12-25 17:00:55.294:E / m08pvv(11592):从最后重新排序:48,好的:   682245
12-25 17:00:56.750:E / m08pvv(11592):重新排序自   最后:49,确定:686534
12-25 17:00:56.830:E / m08pvv(11592):   从最后重新排序:50,确定:686821
12-25 17:00:57.310:   E / m08pvv(11592):从最后重新排序:51,确定:688002
12-25   17:01:12.191:E / m08pvv(11592):从最后重新排序:52,好的:   733724
12-25 17:01:12.708:E / m08pvv(11592):重新排序自   最后:53,确定:735338
12-25 17:01:13.722:E / m08pvv(11592):   从最后重新排序:54,确定:738839
12-25 17:01:25.240:   E / m08pvv(11592):最后重新订购:55,好的:775645

这意味着,对于775645次成功运行该代码,55次我得到isFinallyExecuted等于true 部分构造的对象。这是可能的,因为我没有使用Volatile.Writenew Person()上存储volatileperson个关键字。
所以,如果你有一些数据竞赛,你将面对它们。