【问题标题】:What is the correct usage of ConcurrentBag?ConcurrentBag 的正确用法是什么?
【发布时间】:2013-03-09 10:21:26
【问题描述】:

我已经在这里阅读过关于ConcurrentBag 的先前问题,但没有找到多线程实现的实际示例。

ConcurrentBag 是一种线程安全的包实现,针对同一线程将同时生产和使用包中存储的数据的场景进行了优化。”

目前这是我代码中的当前用法(这是简化而不是实际代码):

private void MyMethod()
{
    List<Product> products = GetAllProducts(); // Get list of products
    ConcurrentBag<Product> myBag = new ConcurrentBag<Product>();

    //products were simply added here in the ConcurrentBag to simplify the code
    //actual code process each product before adding in the bag
    Parallel.ForEach(
                products,
                new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
                product => myBag.Add(product));

    ProcessBag(myBag); // method to process each items in the concurrentbag
}

我的问题:
这是ConcurrentBag 的正确用法吗?这种场景可以用ConcurrentBag吗?

对我来说,我认为简单的List&lt;Product&gt; 和手动锁定会更好。原因是上面的场景已经打破了“同一个线程将同时生产和消费存储在包中的数据”规则。
另外我还发现,在每个线程中并行创建的ThreadLocal存储在操作后仍然存在(即使线程被重用是这样吗?),这可能会导致不希望的内存泄漏。
我在这个人中是对的吗?或者一个简单的清除或清空方法来删除ConcurrentBag 中的项目就足够了?

【问题讨论】:

  • 我认为在这种情况下,开销和整体性能会比使用同步方法更大。你量过吗?
  • ConcurrentBag 有一个构造函数,它接受 IEnumerable&lt;T&gt; var myBag = new ConcurrentBag&lt;Product&gt;(products);
  • 你好,我的意思是我仍然会使用上面几乎相同的代码,除了我将用 List 替换 ConcurrentBag 并在添加产品时添加一个锁在 Parallel.ForEach 内的 List 中
  • 因为您没有使用相同的线程来添加到包中,因为您将使用 ConcurrentQueue 而不是 ConcurrentBag 获得更好的性能。

标签: c# multithreading concurrency


【解决方案1】:

这看起来可以很好地使用 ConcurrentBag。线程局部变量是包的成员,并且将在包的同时成为垃圾收集的条件(清除内容不会释放它们)。你是对的,一个带锁的简单列表就足够了。如果您在循环中所做的工作非常重要,那么线程同步的类型对整体性能影响不大。在这种情况下,您可能会更自如地使用您熟悉的内容。

另一种选择是使用ParallelEnumerable.Select,它与您想要做的更接近。同样,您将看到的任何性能差异都可能可以忽略不计,坚持您所知道的并没有错。

与往常一样,如果它的性能至关重要,那么没有什么可以替代尝试和衡量。

【讨论】:

    【解决方案2】:

    在我看来 bmm6o 是不正确的。 ConcurrentBag 实例内部包含用于向其中添加项目的每个线程的迷你包,因此项目插入不涉及任何线程锁,因此所有 Environment.ProcessorCount 线程都可以在没有卡住等待和没有任何线程上下文的情况下全面展开开关。迭代收集的项目时可能需要线程同步,但在原始示例中,迭代是在所有插入完成后由单个线程完成的。而且,如果ConcurrentBag使用Interlocked技术作为线程同步的第一层,那么完全不涉及Monitor操作是可能的。

    另一方面,使用通常的List&lt;T&gt; 实例并使用 lock 关键字包装其每个 Add() 方法调用会大大降低性能。首先,由于不断的Monitor.Enter()Monitor.Exit() 调用,每个调用都需要深入内核模式并使用Windows 同步原语。其次,有时一个线程可能会被第二个线程阻塞,因为第二个线程还没有完成添加。

    对我来说,上面的代码是正确使用ConcurrentBag 类的一个很好的例子。

    【讨论】:

    • “每个都需要深入内核模式”是的,不。这是另一个很好的例子,说明为什么衡量性能很重要,而不仅仅是理论。单个内核调用一个除了将元素添加到列表之外什么都不做的锁的可能性很小。
    • 发生内核调用的唯一方法是如果有锁约定(如果每个线程中都完成了不可忽略的工作,则不太可能已经)并且锁的持有时间超过自旋计数(对于单个添加,这又是极不可能的)。但是再次不要相信我,测量
    【解决方案3】:

    如果List&lt;T&gt;Add() 方法周围的锁一起使用,它将使线程等待并降低使用Parallel.ForEach() 的性能增益

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-20
      • 2011-04-15
      • 1970-01-01
      • 2012-10-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多