ConcurrentBag的正确用法是什么?

his*_*a21 50 c# concurrency multithreading

我已经在这里阅读过以前的问题,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
}
Run Code Online (Sandbox Code Playgroud)

我的问题:
这是正确的用法ConcurrentBag吗?ConcurrentBag在这种情况下可以使用吗?

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

bmm*_*m6o 23

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

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

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


小智 5

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

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

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

  • “每个人都需要深入内核模式”是的,不。这是另一个很好的例子,说明为什么衡量性能而不只是理论化很重要。一个内核调用一个除了向列表添加一个元素什么都不做的锁的可能性很小。 (3认同)
  • 内核调用发生的唯一方式是,如果有锁约定(如果在每个线程中完成不可忽略的工作,则不太可能已经)*并且*锁的持有时间比自旋计数长(对于单个添加,这又是极不可能的)。但再次不要相信我,*衡量* (2认同)