2012-07-27 45 views
17

非常簡單:除了ConcurrentDictionary(如果必須使用它,但它不是真正的正確概念),是否有任何Concurrent集合(IProducerConsumer實現)支持根據項目的簡單等同性來移除特定項目,或者定義移除條件的謂詞?支持刪除指定項目的併發收集?

說明:我有一個多線程,多階段的工作流程算法,它從數據庫中提取對象,並將它們放在「開始」隊列中。從那裏他們被下一個階段抓住,進一步工作,並塞進其他隊列。這個過程經過幾個階段。與此同時,第一階段由主管再次調用並將對象從數據庫中提取出來,這些對象可以包含仍在處理中的對象(因爲它們還沒有完成處理,因此沒有用標記集說話他們完成了)。

我正在設計的解決方案是一個「在工作中」的集合;對象在第一階段進行檢索時進入該隊列,並且在工作流程的任何階段完成必要的處理後將其重新保存到DB中作爲「已處理」被刪除。當該對象在該列表中時,如果它被第一階段重新檢索,它將被忽略。我已經計劃使用ConcurrentBag,但唯一的刪除方法(TryTake)會從包中刪除任意項目,而不是指定的項目(並且在.NET 4中ConcurrentBag是緩慢的)。 ConcurrentQueue和ConcurrentStack也不允許刪除它會給你的下一個項目以外的項目,而留下ConcurrentDictionary,它可以工作,但比我需要的要多(我真正需要的是存儲正在處理的記錄的Id;他們不會在工作流程中更改)。

+0

你如何看待使用ReaderWriterLockSlim和一個列表?或者也許滾動你自己的同時收藏 – Frobzig 2012-07-27 21:12:32

+1

@Frobzig - 對溫和感興趣的矛盾。我喜歡Concurrent系列,因爲它們只是工作。涉及很少的代碼。 – KeithS 2012-07-27 21:25:57

回答

14

之所以沒有這樣的數據結構是所有集合有O(n)查找操作時被發現。這些是IndexOf,Remove(element)等。他們都列舉了所有元素,並檢查它們是否相等。

只有散列表的查找時間爲O(1)。在併發場景中,O(n)查找時間會導致集合的非常長的鎖定。其他線程在這段時間內將無法添加元素。

在字典中,只有被哈希命中的單元格纔會被鎖定。其他線程可以繼續添加,而通過散列單元中的元素檢查相等性。

我的建議是繼續並使用ConcurrentDictionary。


順便說一句,你是正確的,ConcurrentDictionary是有點過大的解決方案。你真正需要的是快速檢查一個對象是否在工作。 A HashSet將是一個完美的。它基本上什麼也沒有,然後Add(element),Contains(element)Remove(element)。java中有一個ConcurrentHeshSet實現。對於C#我發現這個:How to implement ConcurrentHashSet in .Net不知道它有多好。

作爲第一步,我仍然會編寫一個包含HashSet接口的包裝,並將其與ConcurrentDictionary關聯起來並運行,然後嘗試不同的實現並查看性能差異。

1

這是真的很難在通用意義上使線程安全的集合。線程安全中涉及到很多因素,這些因素不在圖書館/框架類的責任範圍之內,影響其真正「線程安全」的能力......您指出的缺點之一是表現。不可能編寫一個線程安全的高性能集合,因爲它必須假設最差...

一般推薦的做法是使用任何想要的集合並以線程安全的方式訪問它。這基本上是爲什麼框架中沒有更多的線程安全集合的原因。更多關於這可以在http://blogs.msdn.com/b/bclteam/archive/2005/03/15/396399.aspx#9534371

+0

線程安全訪問集合和訪問線程安全集合之間的區別可能非常大。如果線程的數量很大,並且他們正在進行的操作不是微不足道的,那麼每個等待自己的鎖獲取和清除的線程都可能存在巨大的瓶頸。我想你可以維護一個任意大的鎖對象集合(對應於例如每個散列),但這會很麻煩...... – 2012-07-27 22:18:23

+0

@PatrickM讓一個特定應用程序訪問線程安全集合的操作集合非常多低於在每種情況下使線程安全的集合都可以使用。你應該只需要一個鎖對象來訪問任何給定的對象(集合與否)是線程安全的。所有Concurrent *集合都只使用一個鎖對象。 – 2012-07-28 00:11:08

+0

在很多情況下,設計實現快速線程安全集合並不難,這些集合實現了常規接口的某些子集(例如支持「添加」和按索引編寫的唯一突變方法的「IList 」)。這樣的事情可能比提供更廣泛訪問的集合更有效率,但在框架中很少見。唯一想到的例子是'ConditionalWeakTable',它就像一本字典,但不允許刪除項目。 – supercat 2014-01-06 22:00:30

4

正如已經解釋的那樣,其他帖子默認情況下無法從QueueConcurrentQueue中刪除項目,但實際上最容易的方法是擴展或包裝項目。

public class QueueItem 
{ 
    public Boolean IsRemoved { get; private set; } 
    public void Remove() { IsRemoved = true; } 
} 

而離隊時:

QueueItem item = _Queue.Dequeue(); // Or TryDequeue if you use a concurrent dictionary 
if (!item.IsRemoved) 
{ 
    // Do work here 
} 
+0

此解決方案會導致內存泄漏。 – 2015-09-10 16:45:02

+0

@KirillBestemyanov不,它應該如何導致內存泄漏? – 2015-09-11 16:08:47

+0

您不會從集合中刪除任務,因此此集合所消耗的內存僅因運行而增加。 – 2015-09-12 14:44:52