簡體   English   中英

C#無鎖編碼完整性檢查

[英]C# lock free coding sanity check

更新 :現在使用基於以下注釋的只讀集合

我相信以下代碼應該是線程安全的“無鎖”代碼,但要確保我沒有丟失任何東西...

public class ViewModel : INotifyPropertyChanged
{
   //INotifyPropertyChanged and other boring stuff goes here...

   private volatile List<string> _data;
   public IEnumerable<string> Data
   {
      get { return _data; }
   }

   //this function is called on a timer and runs on a background thread
   private void RefreshData()
   {
      List<string> newData = ACallToAService();
      _data = newData.AsReadOnly();
      OnPropertyChanged("Data"); // yes, this dispatches the to UI thread
   }
}

具體來說,我知道我可以使用lock(_lock)甚至是Interlocked.Exchange()但是我不認為在這種情況下需要使用它。 volatile關鍵字應該足夠了(以確保不緩存該值),不是嗎? 有人可以確認一下,還是讓我知道我對線程不了解的內容:)

我不知道那是否“安全”。 這完全取決於您所指的“安全”。 例如,如果將“安全”定義為“保證從所有線程都能觀察到所有易失性寫入的一致順序”,那么就不能保證您的程序在所有硬件上都是“安全”的。

此處的最佳做法是使用鎖,除非您有非常充分的理由不這樣做。 您編寫此危險代碼的極好的理由是什么?

更新:我的觀點是,低鎖或無鎖代碼極具風險,並且世界上只有很少一部分人真正理解它。 讓我舉一個喬·達菲的例子:

// deeply broken, do not use!
class Singleton {
    private static object slock = new object();
    private static Singleton instance;
    private static bool initialized;
    private Singleton() {}
    public Instance {
        get {
            if (!initialized) {
                lock (slock) {
                    if (!initialized) {
                        instance = new Singleton();
                        initialized = true;
                    }
                }
            }
            return instance;
        }
    }
}

該代碼已損壞; 對於C#編譯器的正確實現,為您編寫一個為實例返回null的程序是完全合法的。 你看到了嗎? 如果沒有,那么您就沒有業務進行低鎖或無鎖編程。 弄錯的。

我本人無法弄清楚這些東西。 它傷了我的大腦。 這就是為什么我嘗試從不進行與專家分析過的標准實踐完全不同的低鎖編程的原因。

這取決於意圖。 列表的獲取/設置是原子的(即使沒有volatile)和非緩存的(volatile),但是調用者可以更改列表,這不能保證線程安全。

還有一種競爭條件可能會丟失數據:

 obj.Data.Add(value);

這里的值很容易被丟棄。

我會使用一個不可變的(只讀)集合。

我相信這本身是安全的(即使沒有volatile), 但是根據其他線程如何使用Data屬性,可能會出現問題。

前提是您可以保證所有其他線程在進行枚舉之前都讀取並緩存一次Data的值(並且不要嘗試將其強制轉換為更廣泛的接口以執行其他操作), 並且不對第二次訪問進行一致性假設到物業,那你應該沒事。 如果您不能做出保證(並且例如,如果一個用戶是通過數據綁定的框架本身,因此是您無法控制的代碼,那么就很難做出保證),那么您就無法說這很安全。

例如,這將是安全的:

foreach (var item in x.Data)
{
   // do something with item
}

這是安全的(前提是不允許JIT優化本地,我認為是這樣):

var data = x.Data;
var item1 = FindItem(data, a);
var item2 = FindItem(data, b);
DoSomething(item1, item2);

上面的兩個可能對過時的數據起作用,但是它將始終是一致的數據。 但這不一定是安全的:

var item1 = FindItem(x.Data, a);
var item2 = FindItem(x.Data, b);
DoSomething(item1, item2);

這可能正在搜索集合的兩個不同狀態(在某個線程替換它之前和之后),因此對每個單獨的枚舉中找到的項目進行操作可能並不安全,因為它們可能彼此不一致。

隨着界面的擴大,問題將變得更加嚴重。 例如。 如果數據公開IList<T> ,則還必須注意Count和indexer操作的一致性。

我認為,如果只有兩個您描述的線程,那么您的代碼是正確且安全的。 而且您也不需要那種揮發性,在這里它是沒有用的。

但是請不要稱其為“線程安全”,因為只有您的兩個線程以特殊方式使用它才安全。

暫無
暫無

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

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