2011-12-23 27 views
3

所以在http://msdn.microsoft.com/en-us/library/xfhwa508.aspx底部我們看到與字典不安全線程 - 讓我們打破一些東西

一個詞典(TKEY的,TValue)可以同時支持多個閱讀器,只要收集不被修改。即便如此,枚舉集合本質上不是一個線程安全的過程。在枚舉與寫入訪問競爭的罕見情況下,集合必須在整個枚舉期間被鎖定。爲了讓集合可以被多個線程讀取和寫入,您必須實現自己的同步。

在我的asp.net應用程序中,我偶爾會看到從System.Collections.Generic.Dictionary`2.Insert中生成的NullReferenceExceptions。

我該如何在沙箱中重新創建?

儘管本地字典被單獨的線程訪問,但以下代碼不會導致該異常。

class Program { 
    static void Main(string[] args) { 
     int maxThreads = 20; 
     Dictionary<Guid, Guid> dict = new Dictionary<Guid, Guid>(); 
     var tws = new threadWithState(); 
     while (true) { 
      for (int i = 0; i < maxThreads * 2; i++) { 
       var thread = new Thread(tws.Work); 
       thread.IsBackground = true; 
       thread.Start(dict); 
      } 
      dict[Guid.NewGuid()] = Guid.NewGuid(); 
     } 
    } 
} 

class threadWithState { 
    public void Work(object dict) { 
     Console.WriteLine((dict as Dictionary<Guid, Guid>).Count); 
     for (int i = 0; i < 1000; i++) { 
      (dict as Dictionary<Guid, Guid>)[Guid.NewGuid()] = Guid.NewGuid(); 
     } 
    } 
} 

回答

3

由於垃圾回收堆鎖,此代碼遭受意外同步。通過注入隨機延遲,您會更快地崩潰,很像實際服務器上發生的情況。這段代碼讓我的機器在幾分之一秒內崩潰,ymmv:

 public void Work(object dict) { 
      var rnd = new Random(); 
      Console.WriteLine((dict as Dictionary<Guid, Guid>).Count); 
      for (int i = 0; i < 1000000; i++) { 
       (dict as Dictionary<Guid, Guid>)[Guid.NewGuid()] = Guid.NewGuid(); 
       for (int ix = 0; ix < rnd.Next(1000); ++ix) ; // NOTE: random delay 
      } 
     } 
+0

這是有道理的,但它產生了一個不同的例外。 IndexOutOfBounds而不是NullReferenceException。 – MushinNoShin 2011-12-23 20:02:08

+0

是的,肯定有不止一種方式會因爲不使用鎖而破壞課程。請注意,您不是模擬閱讀,更可能會生成NRE。我會說,一種失敗模式就夠了。 – 2011-12-23 20:13:16

8

這是線程問題的一部分 - 錯誤不一定可靠地重現。問題在於插入件必須達到特定的競爭條件,這顯然不會在您的測試中發生。當然,足夠的運行可能會導致這種情況發生,但這很難進行可靠的測試。

這就是說,這顯然是一個問題。你應該考慮如何糾正它,而不是重現它。當然,這裏的顯而易見的答案是切換到另一種技術,例如使用ConcurrentDictionary<T,U>代替,這是線程安全的。

+0

哦,我同意簡單地糾正它。我希望能夠證明ConcurrentDictionary 是正確的修復。 因此,工作流程就像是重新創建已損壞的狀態,切換到ConcurrentDictionary並顯示它沒有中斷。 我能做些什麼來增加目標機器指令被調用的機會?我應該產生數十億線程嗎?我是否應該在插入前暫停線程,然後以某種方式發信號通知他們打開插入閘門並跨過我的手指?這將如何完成? – MushinNoShin 2011-12-23 19:01:58

+0

@MushinNoShin您可能需要在線程中大幅增加循環的大小。更多的線程並不是問題 - 儘管如此,它們的運行時間不夠長。嘗試插入(並刪除?)一百萬,或千萬個項目,而不是一千... – 2011-12-23 19:05:57