簡體   English   中英

如何使該實例原始線程安全地讀取而不鎖定?

[英]How to make reading this instance primitive thread-safe without locking?

下面的類的問題是,在讀取myThreadSafe.Value它可能不會返回最新的值。

public class ThreadSafe
{   
    private int value;
    public int Value { get { return value; } }

    public void Update()
    {
        Interlocked.Add(ref value, 47); // UPDATE: use interlocked to not distract from the question being asked.
    }
}

我意識到我可以在閱讀和編寫它時鎖定:

public int Value { get { lock(locker) return value; } }

public void Update()
{
    lock(locker)
    {
        value += 47;        
    }
}

而且我一直遵循始終使用鎖的這種模式。 但是我試圖減少代碼中的鎖數量(有很多鎖,它們經常被調用,我已經進行了分析,並且Montior.Enter()占用了我想要的更多時間,因為它被調用了很多次) 。

更新 :我現在想知道,該鎖是否真的對確保我正在讀取最新值有什么不同,它仍然可能來自計算機的CPU緩存之一,不是嗎? (所有鎖定保證是互斥線程訪問)。

我以為volatile將是答案, MSDN確實說:“這確保了字段中始終存在最新值”,但是當我使用volatile時,我在其他地方讀了寫然后讀了CPU指令仍然可以交換在這種情況下,我可以獲得myThreadSafe.Value的先前值,也許我可以忍受它-僅在一次更新中被淘汰。

我將始終獲得myThreadSafe.Value的最新值的最有效方法是什么?

更新:此代碼將被編譯並在CPU體系結構上運行:

  • 86
  • AMD64(盡管我可以將其構建為x86)
  • PowerPC的
  • ARM(僅小端)

使用運行時:

  • CLR v4.0
  • Mono(我不確定Mono運行時版本,但是否與Mono版本相對應:至少為3.0)。

我希望所有構建都使用相同的代碼!

好的,我相信我找到了答案,並且我的擔憂得到了證明!

該代碼恰好在x86和AMD64上是線程安全的,因為它們在寫入變量時使CPU緩存無效,從而導致后續的讀取從內存中讀取變量。 引用Shafqay Ahmed 引用 Jeffrey Richter的話:

由於兩個處理器可以具有不同的緩存(它們是ram的副本),因此它們可以具有不同的值。 在x86和x64中,處理器(根據Jeffrey的書)被設計為同步不同處理器的緩存,因此我們可能看不到問題。

順便說一句,使用lockInterlocked從緩存中清除變量,因此在讀取屬性時使用lock是安全的。 來自http://blogs.msdn.com/b/ericlippert/archive/2011/06/16/atomicity-volatility-and-immutability-are-different-part-three.aspx

鎖可確保觀察到在鎖內讀取或修改的內存是一致的,鎖可確保一次只有一個線程訪問給定的內存塊,依此類推。

但是,當讀取另一個線程(不使用鎖定同步結構)更新的值是最新的值時,CLR規范中無法保證。 實際上,在ARM上,我可以使用ThreadSafe類從http://msdn.microsoft.com/zh-cn/magazine/jj553518.aspx中獲得舊值:

如果您的代碼依賴於依賴於x86 CLR的實現(而不是ECMA CLR規范)的無鎖算法,則需要將volatile關鍵字適當地添加到相關變量中。 將共享狀態標記為易失性后,CLR將為您處理所有事情。 如果您像大多數開發人員一樣,可以在ARM上運行,因為您已經使用鎖來保護共享數據,正確標記了易失變量並在ARM上測試了您的應用。

因此似乎答案是我可以在閱讀時使用lock或使字段volatile ,盡管我可能應該使用鎖定並嘗試減少調用次數,正如從事編譯器工作的人說的

鎖太慢的情況的數量非常少,並且由於您不了解確切的內存模型而導致代碼出錯的可能性非常大。 除了互鎖操作的最簡單用法之外,我不會嘗試編寫任何低鎖代碼。 我將“易失性”的用法留給真正的專家使用。

我不確定“最新值”是什么意思。 您可以使用鎖來確保在寫入Value時不會同時讀取它,這可能會產生一些奇怪的問題,但是如果您先讀取它然后再寫入它,則不會獲得最新的值。

要處理我提到的奇怪問題,您可以像使用鎖一樣使用鎖。 但是您似乎想要一個不同的解決方案。 如果您不想鎖定讀取,但是要確保寫入是原子的,以便在多線程寫入期間進行讀取時讀取不會返回奇數或其他雜亂的事物,那么我建議使用Interlocked類。

只是:

Interlocked.Add(ref value, 47);

可以在http://msdn.microsoft.com/zh-cn/library/system.threading.interlocked(v=vs.110).aspx中找到更多Interlocked功能

當使用基元時,這些功能非常有用。 對於更復雜的對象,將需要其他解決方案,例如ReaderWriterLockSlim和其他解決方案。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM