改進ConcurrentDictionary並行使用的性能


上一篇文章“ConcurrentDictionary 對決 Dictionary+Locking”中,我們知道了 .NET 4.0 中提供了線程安全的 ConcurrentDictionary<TKey, TValue> 類型,並在某些特定的使用條件下會產生問題。

在 ConcurrentDictionary<TKey, TValue> 類中有一個方法 GetOrAdd ,用於嘗試獲取一個鍵值,如果鍵值不存在則添加一個。其方法簽名如下:

public TValue GetOrAdd(
  TKey key,
  Func<TKey, TValue> valueFactory
)

Parameters
key
  Type: TKey
  The key of the element to add.

valueFactory
  Type: System.Func<TKey, TValue>
  The function used to generate a value for the key

通常,我們會通過如下這種方式來使用:

      ConcurrentDictionary<string, ExpensiveClass> dict1
        = new ConcurrentDictionary<string, ExpensiveClass>();

      string key1 = "111111";
      ExpensiveClass value1 = dict1.GetOrAdd(
        key1, 
        (k) => new ExpensiveClass(k));

這種使用方式會產生一個問題,就是如果特定的類的構造過程比較昂貴(資源消耗、時間消耗等),在並行運行條件下,當第一個線程嘗試獲取該鍵值時,發現不存在后開始構建該對象,而在構建的同時,另外一個線程也嘗試獲取該鍵值,發現不存在后也開始構建該對象,當第一個線程構造完畢后將對象添加至字典中,而第二個對象也構造完畢后會再次檢測字典中是否存在該鍵值,因為鍵值已經存在,所以將剛創建完畢的對象直接丟棄,而使用已存在的對象,這造成了對象構造過程中的浪費。如果是關注性能和資源的應用,此處就是一個需要改進的點。

我們假設這個類叫 ExpensiveClass 。

  public class ExpensiveClass
  {
    public ExpensiveClass(string id)
    {
      Id = id;

      Console.WriteLine(
        "Id: [" + id + "] called expensive methods " +
        "which perhaps consume a lot of resources or time.");
    }

    public string Id { get; set; }
  }

類實例化的構造過程為什么昂貴可能有很多中情況,最簡單的例子可以為:

  • 訪問了數據庫,讀取了數據,並緩存了數據。
  • 訪問了遠程服務,讀取了數據,並緩存了數據。
  • 將磁盤中的數據加載到內存中。

改進方式1:使用Proxy模式

我們可以使用 Proxy 模式來包裝它,通過 Proxy 中間的代理過程來隔離對對象的直接創建。

 1   public class ExpensiveClassProxy
 2   {
 3     private string _expensiveClassId;
 4     private ExpensiveClass _expensiveClass;
 5 
 6     public ExpensiveClassProxy(string expensiveClassId)
 7     {
 8       _expensiveClassId = expensiveClassId;
 9     }
10 
11     public ExpensiveClass XXXMethod()
12     {
13       if (_expensiveClass == null)
14       {
15         lock (_expensiveClass)
16         {
17           if (_expensiveClass == null)
18           {
19             _expensiveClass = new ExpensiveClass(_expensiveClassId);
20           }
21         }
22       }
23       return _expensiveClass;
24     }
25   }

改進方式2:使用Lazy<T>模式

這種方式簡單易用,並且同樣解決了問題。

1       ConcurrentDictionary<string, Lazy<ExpensiveClass>> dict2
2         = new ConcurrentDictionary<string, Lazy<ExpensiveClass>>();
3 
4       string key2 = "222222";
5       ExpensiveClass value2 = dict2.GetOrAdd(
6         key2,
7         (k) => new Lazy<ExpensiveClass>(
8           () => new ExpensiveClass(k)))
9         .Value;

在並行的條件下,同樣也存在構造了一個 Lazy<ExpensiveClass> 然后丟棄的現象,所以這種方式是建立在,構造 Lazy<T> 對象的成本要小於構造 ExpensiveClass 的成本。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM