簡體   English   中英

引用分配是原子的,為什么需要 Interlocked.Exchange(ref Object, Object) ?

[英]reference assignment is atomic so why is Interlocked.Exchange(ref Object, Object) needed?

在我的多線程 asmx Web 服務中,我有一個我自己類型 SystemData 的類字段 _allData ,它由幾個標記為volatile List<T>Dictionary<T> 系統數據 ( _allData ) 會_allData刷新一次,我通過創建另一個名為newData對象並用新數據填充它的數據結構來實現。 當它完成時,我只是分配

private static volatile SystemData _allData

public static bool LoadAllSystemData()
{
    SystemData newData = new SystemData();
    /* fill newData with up-to-date data*/
     ...
    _allData = newData.
} 

這應該可以工作,因為分配是原子的,並且引用舊數據的線程繼續使用它,其余的線程在分配后立即擁有新的系統數據。 但是我的同事說我應該使用InterLocked.Exchange而不是使用volatile關鍵字和簡單的賦值,因為他說在某些平台上不能保證引用賦值是原子的。 此外:當我the _allData字段聲明為volatile

Interlocked.Exchange<SystemData>(ref _allData, newData); 

產生警告“對易失性字段的引用不會被視為易失性”我應該怎么想?

這里有很多問題。 一次考慮一個:

引用分配是原子的,為什么需要 Interlocked.Exchange(ref Object, Object) ?

引用分配是原子的。 Interlocked.Exchange 不只做引用分配。 它讀取變量的當前值,隱藏舊值,並將新值分配給變量,所有這些都是原子操作。

我的同事說在某些平台上不能保證引用分配是原子的。 我的同事是對的嗎?

否。引用分配保證在所有 .NET 平台上都是原子的。

我的同事正在從錯誤的前提進行推理。 這是否意味着他們的結論是錯誤的?

不必要。 你的同事可能會出於不好的原因給你很好的建議。 也許您應該使用 Interlocked.Exchange 的原因還有其他一些。 無鎖編程是非常困難的,一旦你偏離了該領域專家所支持的成熟實踐,你就會陷入困境並冒着最糟糕的競爭條件的風險。 我既不是該領域的專家,也不是您的代碼方面的專家,因此我無法以任何方式做出判斷。

產生警告“對易失性字段的引用不會被視為易失性”我應該怎么想?

你應該明白為什么這是一個普遍的問題。 這將導致理解為什么警告在這種特殊情況下不重要。

編譯器給出這個警告的原因是因為將一個字段標記為 volatile 意味着“這個字段將在多個線程上更新——不要生成任何緩存這個字段值的代碼,並確保任何讀取或寫入該字段不會通過處理器緩存不一致“在時間上向前和向后移動”。

(我假設您已經了解了所有這些。如果您沒有詳細了解 volatile 的含義以及它如何影響處理器緩存語義,那么您就不了解它的工作原理,也不應該使用 volatile。無鎖程序很難做到正確;確保您的程序正確,因為您了解它的工作原理,而不是偶然正確。)

現在假設您通過將 ref 傳遞給該字段來創建一個變量,該變量是 volatile 字段的別名。 在被調用的方法中,編譯器沒有任何理由知道引用需要具有可變語義! 編譯器會高興地為無法實現易失性字段規則的方法生成代碼,但該變量易失性字段。 這會完全破壞你的無鎖邏輯; 假設始終是始終使用 volatile 語義訪問 volatile 字段。 有時將其視為 volatile 而不是其他時候是沒有意義的; 您必須始終保持一致,否則您無法保證其他訪問的一致性。

因此,當您這樣做時,編譯器會發出警告,因為它可能會完全弄亂您精心開發的無鎖邏輯。

當然,Interlocked.Exchange編寫為期望一個易變的字段並做正確的事情。 因此,該警告具有誤導性。 我非常后悔; 我們應該做的是實現某種機制,通過這種機制,像 Interlocked.Exchange 這樣的方法的作者可以在方法上放置一個屬性,說“這個采用 ref 的方法對變量強制執行易失性語義,因此禁止警告”。 也許在編譯器的未來版本中我們會這樣做。

要么你的同事弄錯了,要么他知道 C# 語言規范不知道的東西。

變量引用的原子性

“以下數據類型的讀寫是原子的:bool、char、byte、sbyte、short、ushort、uint、int、float 和引用類型。”

因此,您可以寫入 volatile 引用而不會冒損壞值的風險。

您當然應該小心決定哪個線程應該獲取新數據,以盡量減少一次多個線程執行該操作的風險。

Interlocked.Exchange<T>

將指定類型 T 的變量設置為指定值並返回原始值,作為原子操作。

它改變並返回原始值,它沒有用,因為你只想改變它,正如 Guffa 所說,它已經是原子的。

除非分析器證明它是您的應用程序中的瓶頸,否則您應該考慮取消鎖定,這樣更容易理解並證明您的代碼是正確的。

Iterlocked.Exchange()不僅是原子的,它還負責內存可見性:

以下同步函數使用適當的屏障來確保內存排序:

進入或離開臨界區的函數

通知同步對象的函數

等待功能

聯鎖功能

同步和多處理器問題

這意味着除了原子性之外,它還確保:

  • 對於調用它的線程:
    • 沒有重新排序指令(由編譯器、運行時或硬件)。
  • 對於所有線程:
    • 在此指令之前從內存中讀取不會看到在此指令之后發生的內存更改(由調用此指令的線程)。 這聽起來很明顯,但緩存行可能會按照寫入的順序刷新到主內存。
    • 此指令之后的所有讀取都將看到此指令所做的更改以及此指令之前(由調用此指令的線程)所做的所有更改。
    • 在此指令更改到達主內存后,將在此指令之后寫入內存(通過在完成時將此指令更改刷新到主內存,而不是讓硬件刷新它自己的定時)。

暫無
暫無

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

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