[英]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 引用而不會冒損壞值的風險。
您當然應該小心決定哪個線程應該獲取新數據,以盡量減少一次多個線程執行該操作的風險。
將指定類型 T 的變量設置為指定值並返回原始值,作為原子操作。
它改變並返回原始值,它沒有用,因為你只想改變它,正如 Guffa 所說,它已經是原子的。
除非分析器證明它是您的應用程序中的瓶頸,否則您應該考慮取消鎖定,這樣更容易理解並證明您的代碼是正確的。
Iterlocked.Exchange()
不僅是原子的,它還負責內存可見性:
以下同步函數使用適當的屏障來確保內存排序:
進入或離開臨界區的函數
通知同步對象的函數
等待功能
聯鎖功能
這意味着除了原子性之外,它還確保:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.