2017-04-05 66 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

所以,如果你有一些數據競賽,你會面對他們。