簡體   English   中英

C#中的易失性字段

[英]Volatile fields in C#

從規范10.5.3揮發性字段:


volatile字段的類型必須是以下之一:

  • 引用類型。

  • 類型byte,sbyte,short,ushort,int,uint,char,float,bool,System.IntPtr或System.UIntPtr。

  • 具有枚舉基類型byte,sbyte,short,ushort,int或uint的枚舉類型。


首先,我想確認我的理解是正確的:我猜上面的類型可能是易失性的,因為它們作為4字節單元存儲在內存中(因為它的地址對於引用類型),這保證了讀/寫操作是原子的。 double / long / etc類型不能是volatile,因為它們不是原子讀/寫,因為它們在內存中超過4個字節。 我的理解是否正確?

第二個,如果第一個猜測是正確的,為什么用戶定義的結構只有一個int字段(或類似的東西,4個字節是可以的)不能是volatile? 理論上它是原子的嗎? 或者僅僅是因為所有用戶定義的結構(可能超過4個字節)不允許設計的易失性?

基本上,使用volatile關鍵字有時會產生誤導。 其目的是允許在任何線程訪問時返回相應成員的最新值 (或實際上,最終足夠新鮮的值) 1

實際上,這僅適用於值類型 2 引用類型成員在內存中表示為指向堆中實際存儲對象的位置的指針。 因此,當在引用類型上使用時, volatile確保您只獲取對象的引用(指針)的新值,而不是對象本身。

如果你有一個volatile List<String> myVolatileList ,它由多個線程通過添加或刪除元素來修改 ,並且如果你希望它安全地訪問列表的最新修改,那么你實際上是錯的 事實上,你容易出現同樣的問題,就好像volatile不存在 - 競爭條件和/或對象實例已損壞 - 在這種情況下它不會幫助你,它既不會為你提供任何線程安全性。

但是,如果列表本身沒有被不同的線程修改,而是每個線程只會為該字段分配一個不同的實例(意味着列表的行為類似於不可變對象),那么你沒事。 這是一個例子:

public class HasVolatileReferenceType
{
    public volatile List<int> MyVolatileMember;
}

以下用法對於多線程是正確的,因為每個線程都將替換MyVolatileMember指針。 這里, volatile確保其他線程將看到存儲在MyVolatileMember字段中的最新列表實例。

HasVolatileReferenceTypeexample = new HasVolatileReferenceType();
// instead of modifying `example.MyVolatileMember`
// we are replacing it with a new list. This is OK with volatile.
example.MyVolatileMember = example.MyVolatileMember
     .Where(x => x > 42).ToList();

相反,下面的代碼容易出錯,因為它直接修改了列表。 如果此代碼與多個線程同時執行,則列表可能會損壞,或者行為方式不一致。

example.MyVolatileMember.RemoveAll(x => x <= 42);

讓我們暫時回到值類型。 在.NET中,所有值類型在修改時實際上都被重新分配 ,它們可以安全地與volatile關鍵字一起使用 - 請參閱代碼:

public class HasVolatileValueType
{
    public volatile int MyVolatileMember;
}

// usage
HasVolatileValueType example = new HasVolatileValueType();
example.MyVolatileMember = 42;

1正如Eric Lippert評論部分所指出的,這里的lates值的概念有點誤導。 事實上,這里的最新意味着.NET運行時將嘗試 (此處不保證)防止在讀取操作之間發生對易失性成員的寫入,只要它認為可能。 這將有助於不同的線程讀取volatile成員的新值,因為它們的讀操作可能在對成員的寫操作之后被排序。 但是在這里還有更多值得信賴的概率。

2通常, volatile可以在任何不可變對象上使用,因為修改總是意味着使用不同的值重新分配字段。 以下代碼也是使用volatile關鍵字的正確示例:

public class HasVolatileImmutableType
{
    public volatile string MyVolatileMember; 
}

// usage
HasVolatileImmutableType example = new HasVolatileImmutableType();
example.MyVolatileMember = "immutable";
// string is reference type, but is *immutable*, 
// so we need to reasign the modification result it in order 
// to work with the new value later
example.MyVolatileMember = example.MyVolatileMember.SubString(2);

我建議你看一下這篇文章 它徹底解釋了volatile關鍵字的用法,實際工作方式以及使用它的可能后果。

所以,我想你提出以下幾點要補充:

  • 一種值類型,僅包含一個可以合法標記為volatile的字段。

首先,字段通常是私有的,因此在外部代碼中,任何東西都不應該依賴於某個字段的存在。 盡管編譯器在訪問私有字段時沒有問題,但根據程序員沒有適當的方法來影響或檢查某些特性並不是一個好主意。

由於字段通常是類型內部實現的一部分,因此可以在引用的程序集中隨時更改它,但這可能會使得使用該類型的C#代碼非法。

這個理論和實踐的原因意味着唯一可行的方法是為值類型引入一個volatile修飾符,以確保上面指定的點成立。 但是,由於唯一可以從此修飾符中受益的類型組是具有單個字段的值類型,因此列表中的此功能可能不是很高。

這是一個有根據的猜測答案...如果我錯了,請不要把我擊倒太多!

易失性狀態的文檔

volatile修飾符通常用於多個線程訪問的字段,而不使用lock語句來序列化訪問。

這意味着volatile字段的部分設計意圖是實現無鎖多線程訪問。

結構的成員可以獨立於其他成員進行更新。 因此,為了編寫只有部分更改的新結構值,必須讀取舊值。 因此,不能保證寫入需要單個存儲器操作。 這意味着為了在多線程環境中可靠地更新結構,需要某種鎖定或其他線程同步。 在沒有同步的情況下從多個線程更新多個成員很快就會導致反直覺(如果不是技術上的損壞)結果:使結構易失性將標記非原子對象作為原子可更新。

此外,只有一些結構可能是易失性的 - 大小為4字節。 確定大小的代碼 - 結構定義 - 可以在程序的完全獨立的部分中,也可以將字段定義為volatile。 這可能會令人困惑,因為更新結構定義會產生意想不到的后果。

因此,盡管技術上可以允許一些結構易變,但正確使用的警告將足夠復雜,以至於缺點將超過其益處。

我建議的解決方法是將4字節結構存儲為4字節基本類型,並實現靜態轉換方法,以便每次使用該字段時使用。

為了解決問題的第二部分,我會基於兩點支持語言設計者的決定:

親吻 - 保持簡單西蒙 - 它會使規范更加復雜,並且很難實現此功能。 所有語言功能都從零下100點開始,增加了少數struts volatile能否真正值得101分的能力?

兼容性 - 除了序列化的問題 - 通常向類型[class,struct]添加新字段是一種安全的向后源兼容移動。 如果你添加一個字段不應該破壞anyones編譯。 如果在添加字段時結構的行為發生了變化,則會破壞它。

我認為這是因為結構是一種值類型,它不是規范中列出的類型之一。 有趣的是,引用類型可以是易失性字段。 所以它可以通過用戶定義的類來完成。 這可能反駁了您的理論,即上述類型是易失性的,因為它們可以存儲在4個字節(或者可能不存儲)中。

暫無
暫無

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

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