在ConcurrentBag<T>中使用Parallel.ForEach是线程安全的吗?

16

MSDN上关于ConcurrentBag的描述不清楚:

Bag适用于存储对象,当顺序无关紧要时,与集合不同,Bag支持重复项。ConcurrentBag是一种线程安全的Bag实现,针对同一线程既会生产又会消费存储在Bag中的数据的场景进行了优化。

我的问题是它是否线程安全,以及在Parallel.ForEach中使用ConcurrentBag是否是一个好的做法。

例如:

    private List<XYZ> MyMethod(List<MyData> myData)
    {
        var data = new ConcurrentBag<XYZ>();

        Parallel.ForEach(myData, item =>
                        {
                            // Some data manipulation

                            data.Add(new XYZ(/* constructor parameters */);
                        });

        return data.ToList();
    }

这样我就不必在使用普通列表的Parallel.ForEach中使用同步锁。

非常感谢。

2个回答

12

我认为这看起来很好。你使用的方式是线程安全的。

如果你能返回一个 IEnumerable<XYZ>,当完成时不需要复制到 List<T>,它可以变得更加高效。


这个改动真的有任何性能上的提升吗?.Add函数不会在同步时保持线程安全吗?(假设注释掉的代码需要0ms才能完成。) - Sprague
并发集合使用无锁和部分锁定技术的混合。它们不会为每个操作锁定整个集合。 - Stephen Cleary
感谢您澄清。对于那些感兴趣的人,我实际上运行了这个基准测试,并反编译了CLR代码。它使用了Interlocked.Exchange和Monitor.Enter。我想这就是您所指的部分锁定吧?无论如何,并行代码几乎每次都比较快,只有在迭代数量非常少且延迟很小的情况下,“普通”的方法才能提高性能。我不确定我会自己使用这种技术(不要忘记注释掉的代码需要线程安全!),但它确实很快。 - Sprague

0

对于ConcurrentBag和Parallel.ForEach,我觉得没有问题。但如果你在大量多用户访问的情况下使用这些类型,在实现中这些类可能会使CPU进程升高到可以导致Web服务器崩溃的水平。此外,该实现启动N个任务(线程)来执行每个迭代中的一个,因此在选择这些类和实现时要小心。最近我遇到了这种情况,不得不提取内存转储以分析我的Web应用程序核心发生了什么。所以,在Web场景中,要小心使用Concurrentbag,虽然它是线程安全的,但并不是最好的方式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接