2017-04-05 97 views
1

在一篇文章中有关A scalable reader/writer scheme with optimistic retry有一个代码示例:C#可以写入指令从finally块重新排序到try块吗?

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之后执行,但finally块在list of implicit memory barriers中未提及。 如果来自finally的指令可能会在try之前被移动,但是在指令级别对CPU的优化对我来说仍然是一个黑魔法,这将是相当混淆的

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

示例中显示的MemoryBarrier是隐式的,并且由作者锁定的方法生成,所以它们始终存在。危险是读者在作家完成之前可以看到m_version2递增。

+0

发布此消息后,我已阅读http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf。它看起来像Volatile.Write是需要的,没有特别的处理'finally'(I.12.6-7节)。除非我错过了关于CERs的一些细节。 –

回答

1

我没有找到任何规范,这将限制它,所以我与ARM CPU设备(使用Xamarin,必须检查它的核心CLR)检查的话......
一个线程正在执行此代码:

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

而第二个线程在等待isFinallyExecutedtrue与此代码:

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

那么第二个线程w ^作为执行以下代码:

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,OK: 12-25 17 :00:56.750:E/m08pvv(11592):从 最后重新排序:49,ok:686534
12-25 17:00:56.830:E/m08pvv(11592): 从最后重新排序:50,ok:686821
12-25 17:00:57.310: E/m08pvv(11592):从最后重新排序:51,ok:688002
12-25 17:01:12.191:E/m08pvv(11592):从最后重新排序:52,好的: 12-25 17:01:12.708:E/m08pvv(11592):从 重新排序:53,ok:735338
12-25 17:01:13。722:E/m08pvv(11592): 从最后重新排序:54,OK:738839
12-25 17:01:25.240: E/m08pvv(11592):从最后重新排序:55,OK:775645

这意味着对于该代码的775645次成功运行,55次我得到isFinallyExecuted等于true部分构建的对象。这是可能的,因为我不使用Volatile.Write来存储new Person()volatile关键字person

所以,如果你有一些数据竞赛,你会面对他们。