2009-11-06 54 views
11

我發現自己在即將到來的.NET 4.0框架ConcurrentBag<T>階層的存在很好奇:像.NET的ConcurrentBag <T>這樣的類會如何實現?

袋子是用於存儲訂貨時沒有關係的對象很有用,不像套,包支持重複。

我的問題是:這個想法如何實現?我熟悉的大多數集合實質上(暗中)某種形式的數組,其順序可能並不重要,但的順序(這就是爲什麼,即使它不需要,枚舉將幾乎總是經歷一個未改變的集合,例如ListQueue,Stack等等)。

如果我不得不猜測,我可能會建議在內部它可能是Dictionary<T, LinkedList<T>>;但是這實際上似乎很可疑,因爲只使用任何類型T作爲關鍵是沒有意義的。

我期待/希望的是,這實際上是一個既定的對象類型,已經在某個地方「弄清楚」了,知道這種建立類型的人可以告訴我這個類型。對我來說這很不尋常 - 這些概念在現實生活中很容易理解,但作爲開發人員很難轉化爲可用的類 - 這就是爲什麼我對可能性感到好奇的原因。

編輯

一些反應表明,一個Bag可能是一個哈希表的形式在內部。這是我最初的想法是很好,但我預見到兩個問題的想法:

  1. 散列表是不是所有的有用的,當你沒有有問題的類型合適的哈希碼功能。
  2. 在集合中跟蹤對象的「數量」與存儲對象並不相同。

爲元騎士建議,也許一個例子將使它更加明確:

public class ExpensiveObject() { 
    private ExpensiveObject() { 
     // very intense operations happening in here 
    } 

    public ExpensiveObject CreateExpensiveObject() { 
     return new ExpensiveObject(); 
    } 
} 

static void Main() { 
    var expensiveObjects = new ConcurrentBag<ExpensiveObject>(); 

    for (int i = 0; i < 5; i++) { 
     expensiveObjects.Add(ExpensiveObject.CreateExpensiveObject()); 
    } 

    // after this point in the code, I want to believe I have 5 new 
    // expensive objects in my collection 

    while (expensiveObjects.Count > 0) { 
     ExpensiveObject expObj = null; 
     bool objectTaken = expensiveObjects.TryTake(out expObj); 
     if (objectTaken) { 
      // here I THINK I am queueing a particular operation to be 
      // executed on 5 separate threads for 5 separate objects, 
      // but if ConcurrentBag is a hashtable then I've just received 
      // the object 5 times and so I am working on the same object 
      // from 5 threads at the same time! 
      ThreadPool.QueueUserWorkItem(DoWorkOnExpensiveObject, expObj); 
     } else { 
      break; 
     } 
    } 
} 

static void DoWorkOnExpensiveObject(object obj) { 
    ExpensiveObject expObj = obj as ExpensiveObject; 
    if (expObj != null) { 
     // some work to be done 
    } 
} 
+0

+1,因爲它是很好的知道這個類的存在 – Konamiman 2009-11-06 16:54:27

+0

Dan-o:您的示例代碼中的5行註釋是沒有意義的。當然,在這個點你有5個獨立的對象。[public ExpensiveObject CreateExpensiveObject()]中的「new」操作符保證。 – Boogaloo 2009-11-07 00:24:22

+0

嗯..我的錯誤。我以前沒有使用過散列。我假定默認的散列生成器會爲每個對象創建一個唯一的散列值 - 您可以使用自己的散列生成器來覆蓋它。別介意我。 :) – Boogaloo 2009-11-12 00:51:15

回答

8

如果你看看ConcurrentBag<T>細節,你會發現它的,在內部,基本上是一個自定義的鏈接名單。

由於Bags可以包含重複項,並且不能通過索引訪問,雙向鏈接列表是實現的一個非常好的選項。這允許鎖定對於插入和移除來說是相當細緻的(您不必鎖定整個集合,只需要在插入/移除位置附近的節點)。既然你不擔心重複,不涉及散列。這使得雙鏈表完美。

+0

好點:我甚至沒有想過一個包不需要做任何匹配的事實 - 它只需要對象並返回它們。在我看來,似乎一個真正混淆的問題似乎並不那麼令人費解。 – 2009-11-06 17:01:59

+0

由於其他響應者有不同的答案(雖然你對我最有意義),但我很想知道你在哪裏找到這些細節?此外,這是一個很好的觀點,只需要鎖定發生插入/刪除操作的節點......然後,我很想知道,實際上是'ConcurrentQueue ''在內部還有'LinkedList '' ? (否則,排隊/出隊似乎是不必要的昂貴)。 – 2009-11-06 17:42:34

+0

您可以在.NET 4 beta 2中的System.dll上使用Reflector - 您將看到它的完整實現。它實際上並不是一個LinkedList ,而是一個內部的ConcurrentBag .ThreadLocalList使用ConcurrentBag .Node - 但它基本上是一個自定義的雙鏈表。 – 2009-11-06 17:55:27

0

由於排序無關緊要,因此ConcurrentBag可能會在後臺使用散列表來允許快速檢索數據。但與Hashset不同,一個包可以接受重複。也許每個項目都可以與添加項目時設置爲1的Count屬性配對。如果第二次添加相同的項目,則可以只增加此項目的Count屬性。

然後,要刪除一個數量大於1的項目,您可以減少此項目的計數。如果計數爲1,您將從哈希表中刪除項目計數對。

+0

聽起來你和我有類似的想法,但考慮一下:首先,這將限制使用'ConcurrentBag '來適合用作鍵的類型。其次,如果你只是在'Hashset'中的每個條目上都有一個'Count'屬性,那麼這個對象並不是真的放在包裏,我覺得它基本上沒有達到目的。 (因此,如果我的'ConcurrentBag '中有10份同樣的「Thing」,並且我將'TryTake'調用了10次,第一次之後返回的結果是什麼?同樣的「Thing」?那麼我認爲我有10個對象但真的我只有1.) – 2009-11-06 17:15:18

+0

如果兩個項目在ConcurrentBag中被認爲是重複的,那麼它們是不是相同?如果它們是相同的,那麼預計不會,如果你幾次調用TryTake,你會得到完全相同的對象?我不確定ConcurrentBag如何與「不適合用作密鑰的類型」配合使用... – 2009-11-06 17:23:38

+0

如果您有一個具體示例,它可以幫助我很好地理解您提到的問題;-) – 2009-11-06 17:24:27

0

那麼,在Smalltalk(Bag的概念來自哪裏)中,該集合基本上與散列相同,儘管允許重複。它不是保存重複對象,而是保持「發生次數」,例如每個對象的重新計數。如果ConcurrentBag是一個忠實的實現,這應該給你一個起點。

0

我相信'Bag'的概念與'Multiset'同義。

如果您對如何實現它們感興趣,有許多「Bag」/「Multiset」實現(這些實際上都是java),它們是開源的。

這些實現表明,'Bag'可以根據您的需要以多種方式實現。有TreeMultiset,HashMultiset,LinkedHashMultiset,ConcurrentHashMultiset的例子。

谷歌集合
谷歌擁有一批"MultiSet" implementations,一個是ConcurrentHashMultiset。

Apache Commons
Apache有許多「Bag」實現。

2

有上ConcurrentBag一些好的信息在這裏:http://geekswithblogs.net/BlackRabbitCoder/archive/2011/03/03/c.net-little-wonders-concurrentbag-and-blockingcollection.aspx

的ConcurrentBag工作 是採取新的 的ThreadLocal類型的優勢(新中 的System.Threading用於.NET 4.0),這樣的方式 使用包的每個線程都有一個列表 本地到該線程。

這意味着向 添加或刪除線程本地列表需要非常低的同步。問題出現在 ,其中一個線程去消費一個項目 ,但它的本地列表是空的。在這個 的情況下,包執行「盜取工作」 ,它將從另一個 線程中搶奪其列表中的項目。 這需要更高級別的 同步,這會使take操作的開銷增加一點點 。