ConcurrentBag的正确用法是什么?

时间:2013-03-20 10:57:16

标签: c# multithreading concurrency

我已经在这里阅读了有关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<Product>和一个手动锁会做得更好。这样做的原因是上面的场景已经打破了“同一个线程将生成并消耗存储在包中的数据”规则。
另外我还发现在并行的每个线程中创建的ThreadLocal存储在操作后仍然存在(即使线程被重用是正确的吗?),这可能会导致意外的内存泄漏。
我是对的吗?或者删除ConcurrentBag中的项目的简单明确或空方法就足够了?

3 个答案:

答案 0 :(得分:20)

这看起来好像使用了ConcurrentBag。线程局部变量是包的成员,并且在包被同时将有资格进行垃圾收集(清除内容不会释放它们)。你是对的,一个带锁的简单List就足以满足你的要求。如果你在循环中所做的工作非常重要,那么线程同步的类型对整体性能来说并不重要。在这种情况下,您可能更习惯使用您熟悉的内容。

另一种选择是使用ParallelEnumerable.Select,它与你想要更紧密地做的事情相匹配。同样,您将看到的任何性能差异可能都可以忽略不计,坚持您所知道的内容并没有错。

与往常一样,如果这一点的表现至关重要,那就无法替代尝试和测量。

答案 1 :(得分:5)

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

另一方面,使用通常的List<T>实例并使用lock关键字将其每个Add()方法调用包装起来会对性能造成很大影响。首先,由于常量Monitor.Enter()Monitor.Exit()调用,每个调用都需要深入到内核模式并使用Windows同步原语。其次,有时偶尔会有一个线程被第二个线程阻塞,因为第二个线程尚未完成其添加。

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

答案 2 :(得分:0)

如果List<T>Add()方法周围的锁一起使用,它将使线程等待并降低使用Parallel.ForEach()的性能。