【问题标题】:Concurrent collection supporting removal of a specified item?支持删除指定项目的并发集合?
【发布时间】:2012-07-26 14:15:06
【问题描述】:

很简单:除了 ConcurrentDictionary(如果我必须使用它,但它不是真正正确的概念)之外,是否有任何 Concurrent 集合(IProducerConsumer 实现)支持基于项目的简单相等来删除特定项目还是定义移除条件的谓词?

说明:我有一个多线程、多阶段的工作流算法,它从数据库中提取对象并将它们放入“开始”队列中。从那里他们被下一阶段抓住,进一步处理,并塞进其他队列。这个过程会持续几个阶段。同时,第一阶段由其主管再次调用并将对象从数据库中拉出,这些对象可能包括仍在处理中的对象(因为它们尚未完成处理,因此尚未使用标志集重新持久化)他们已经完成了)。

我正在设计的解决方案是“工作中”的大师系列;对象在第一阶段检索以进行处理时进入该队列,并在工作流的任何阶段完成必要处理后将它们重新保存到数据库作为“已处理”后将其删除。当对象在该列表中时,如果它被第一阶段重新检索,它将被忽略。

我曾计划使用 ConcurrentBag,但唯一的移除方法 (TryTake) 从包中移除任意项目,而不是指定项目(并且 ConcurrentBag 在 .NET 4 中是 slow)。 ConcurrentQueue 和 ConcurrentStack 也不允许删除它会给你的下一个项目以外的项目,留下 ConcurrentDictionary,它可以工作但超出我的需要(我真正需要的是存储正在处理的记录的 Id;它们在工作流程中不会改变)。

【问题讨论】:

  • 您对使用 ReaderWriterLockSlim 和 List 有什么感觉?或者也许滚动你自己的并发集合
  • @Frobzig - 有点矛盾。我喜欢 Concurrent 集合,因为它们可以正常工作;涉及的代码很少。

标签: c# concurrent-collections


【解决方案1】:

正如其他帖子已经解释的那样,默认情况下无法从 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
}

【讨论】:

  • 这个解决方案会导致内存泄漏。
  • @KirillBestemyanov 不,应该如何导致内存泄漏?
  • 您不会从集合中删除任务,因此该集合所消耗的内存只会因为它正在运行而增加。
  • @KirillBestemyanov 如果你想在项目被删除时删除它们,你不应该使用队列,而是使用其他东西。根据定义,这不是内存泄漏,而是对队列的误用。
  • 你为什么向提问者推荐这个解决方案?如果这是误用,应用程序会在执行过程中慢慢吃掉内存?
【解决方案2】:

之所以没有这样的数据结构,是因为所有的集合都有O(n)的查找操作时间。这些是IndexOfRemove(element) 等。它们都枚举所有元素并检查它们是否相等。

只有哈希表的查找时间为 O(1)。在并发场景中,O(n) 查找时间会导致集合的锁定时间很长。在此期间,其他线程将无法添加元素。

在字典中只有被哈希命中的单元格会被锁定。当一个线程通过哈希单元格中的元素检查相等性时,其他线程可以继续添加。

我的建议是继续使用 ConcurrentDictionary。


顺便说一句,您说得对,ConcurrentDictionary 对于您的解决方案来说有点过大。您真正需要的是快速检查对象是否在工作的天气。 HashSet 将是完美的选择。然后Add(element)Contains(element)Remove(element) 基本上什么都不做。在 java 中有一个 ConcurrentHeshSet 实现。对于 c# 我发现了这个:How to implement ConcurrentHashSet in .Net 不知道它有多好。

作为第一步,我仍然会在ConcurrentDictionary 周围编写一个带有HashSet 接口的包装器,将其启动并运行,然后尝试不同的实现并查看性能差异。

【讨论】:

    【解决方案3】:

    真的很难在一般意义上使集合线程安全。线程安全有很多因素超出了库/框架类的责任或权限范围,这些因素会影响它真正“线程安全”的能力......正如你所指出的那样的缺点之一出来的是性能。编写一个也是线程安全的高性能集合是不可能的,因为它必须假设最坏的情况......

    通常推荐的做法是使用您想要的任何集合并以线程安全的方式访问它。这基本上就是为什么框架中没有更多线程安全集合的原因。更多信息请访问http://blogs.msdn.com/b/bclteam/archive/2005/03/15/396399.aspx#9534371

    【讨论】:

    • 对集合的线程安全访问与访问线程安全集合之间的差异可能相当大。如果线程的数量很大并且它们正在执行的操作不是微不足道的,那么每个等待自己的锁获取和清除的线程都可能存在巨大的瓶颈。我想你可以维护一个任意大的锁对象集合(对应于例如每个哈希),但这会很混乱......
    • @PatrickM 使特定应用程序访问集合线程安全的操作集远低于在每种情况下都可以使用使集合线程安全的操作集。您应该只需要一个锁对象就可以对任何给定对象(集合或非集合)进行线程安全的访问。所有的 Concurrent* 集合只使用一个锁对象。
    • 在许多情况下,设计实现某些正常接口子集的快速线程安全集合并不难(例如,支持AddIList<T> 和按索引写入是唯一的变异方法)。这样的东西可能比提供更通用访问的集合更有效,但在框架中很少见。唯一想到的例子是ConditionalWeakTable,它就像一本字典,但不允许删除项目。
    猜你喜欢
    • 2012-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-31
    • 1970-01-01
    • 2012-03-16
    相关资源
    最近更新 更多