62

我已经在这里阅读了以前的问题,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就足够了?

4

3 回答 3

27

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

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

与往常一样,如果它的性能很关键,那么没有什么可以替代尝试和测量。

于 2013-03-20T15:58:00.107 回答
10

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

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

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

于 2017-10-27T19:01:27.707 回答
0

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

于 2019-02-22T06:00:08.723 回答