2011-05-18 85 views
1

我要讓我的代碼multithreadable,爲此我需要修改字典成ConcurrentDictionary。我看了一下ConcurrentDictionary,查了一些例子,但我仍然需要一隻手放在這樣的:重構字典ConcurrentDictionary

這裏是原來的代碼(單線程)

private IDictionary<string, IDictionary<string, Task>> _tasks; 
public override IDictionary<string, IDictionary<string, Task>> Tasks 
    { 
     get 
     { 
      // return dictionary from cache unless too old 
      // concurrency!! (null check) 
      if (_tasks != null && (DateTime.Now - _lastTaskListRefreshDateTime < TimeSpan.FromSeconds(30))) 
      { 
       return _tasks; 
      } 

      // reload dictionary from database 
      _tasks = new Dictionary<string, IDictionary<string, Task>>(); 

      // find returns an IEnumerable<Task> 
      var tasks = Find<Task>(null, DependencyNode.TaskForCrawler).Cast<Task>(); 

      // build hierarchical dictionary from flat IEnumerable 
      // concurrency!! 
      foreach (var t in tasks) 
      { 

       if (_tasks.ContainsKey(t.Area.Key)) 
       { 
        if (_tasks[t.Area.Key] == null) 
        { 
         _tasks[t.Area.Key] = new Dictionary<string, Task>(); 
        } 

        if (!_tasks[t.Area.Key].ContainsKey(t.Key)) 
        { 
         _tasks[t.Area.Key].Add(t.Key, t); 
        } 
       } 
       else 
       { 
        _tasks.Add(t.Area.Key, new Dictionary<string, Task> { { t.Key, t } }); 
       } 
      } 

      _lastTaskListRefreshDateTime = DateTime.Now; 
      return _tasks; 
     } 

     set 
     { 
      _tasks = value; 
     } 
    } 

這裏是我想出了:

private ConcurrentDictionary<string, ConcurrentDictionary<string, Task>> _tasks = new ConcurrentDictionary<string, ConcurrentDictionary<string, Task>>(); 
public override ConcurrentDictionary<string, ConcurrentDictionary<string, Task>> Tasks 
{ 
     get 
     { 
      // use cache 
      // concurrency?? (null check) 
      if (!_tasks.IsEmpty && (DateTime.Now - _lastTaskListRefreshDateTime < TimeSpan.FromSeconds(30))) 
      { 
       return _tasks; 
      } 

      // reload 
      var tasks = Find<Task>(null, DependencyNode.TaskForCrawler).Cast<Task>(); 

      foreach (var task in tasks) 
      { 
       var t = task; // inner scope for clousure 
       var taskKey = t.Key; 
       var areaKey = t.Area.Key; 

       var newDict = new ConcurrentDictionary<string, Task>(); 
       newDict.TryAdd(taskKey, t); 

       _tasks.AddOrUpdate(areaKey, newDict, (k, v) => { 
                 // An dictionary element if key=areaKey already exists 
                 // extend and return it. 
                 v.TryAdd(taskKey, t); 
                 return v; 
                 }); 
      } 

      _lastTaskListRefreshDateTime = DateTime.Now; 
      return _tasks; 
     } 

我不敢肯定這是它,特別是我相當肯定的是,爲IsEmpty檢查不是線程安全的,因爲_tasks可能已經在IsEmpty檢查和&& ...部分或return _tasks之間初始化部分。我是否必須手動鎖定此檢查?我是否需要雙重鎖定(空檢查>鎖定>空檢查)?

+0

返回部分完成的'_tasks'安全嗎?如果不是的話,當你在循環中填充它時,你將不得不鎖定整個事物。 – Gabe 2011-05-18 11:43:29

回答

1

您的擔心是有道理的。 Tasks屬性獲取器不是線程安全的。這裏有幾個問題。

首先,像你身邊的,還有從一個線程IsEmpty一個電話,從另一個線程去除項目之間的競爭。 getter可以返回一個空字典。

其次,在if支票中的_lastTaskListRefreshDateTime的讀數與吸氣劑末端的分配之間存在競爭。即使這些操作都是原子(它們不能是至少在32位平臺因爲DateTime是64位)還有很微妙存儲器屏障問題,因爲像volatile沒有同步機制是在代碼中顯而易見。

第三,類似於上述我的解釋,存在另一個存儲器屏障問題_tasks參考。一個線程可以調用setter,而另一個線程調用getter。由於不存在內存屏障,因此CLR或硬件可以自由優化讀取和寫入操作,使得setter中所做的更改對getter不可見。這個問題可能不一定會導致任何問題,但我敢打賭,這是沒有預料到的行爲。沒有其他的分析背景,我不能說任何一種方式。

+0

瞭解。所以最好的辦法是鎖定整個Task任務。僅鎖定foreach循環(由Gabe提議)不會用'_lastTaskListRefreshDateTime'解決問題......對吧? – Stefan 2011-05-19 10:32:08

+0

我的解決方案是由Gabe的提案鎖定填充環和Brian的見解,尤其是'_lastTaskListRefreshDateTime'比賽中得到的,所以我想出了是整個吸氣的鎖定。 – Stefan 2011-05-24 06:27:12

1

ConcurrentDictionary只能保證對字典的讀寫操作不會遍佈彼此,類Dictionary類不能做。 ConcurrentDictionary中的線程安全性不會使您的代碼線程安全,它只能確保其代碼是線程安全的。由於這種情況,您需要在吸氣器中鎖定。