使用ConcurrentBag类型作为List的线程安全替代品

本文关键字:线程 安全 替代品 List ConcurrentBag 类型 使用 | 更新日期: 2023-09-27 18:16:54

一般来说,使用ConcurrentBag类型是可以接受的替代List的线程安全类型吗?我在这里读了一些答案,建议在c#中使用通用列表的线程安全问题时使用ConcurrentBag

然而,在阅读了一些关于ConcurrentBag的内容之后,似乎执行大量搜索和遍历集合并不符合其预期用途。它似乎主要是为了解决生产者/消费者问题,其中工作正在(某种程度上随机地)从集合中添加和删除。

这是我想与ConcurrentBag一起使用的(IEnumerable)操作类型的示例:

...
private readonly ConcurrentBag<Person> people = new ConcurrentBag<Person>();
public void AddPerson(Person person)
{
   people.Add(person);
}
public Person GetPersonWithName(string name)
{
   return people.Where(x => name.Equals(x.Name)).FirstOrDefault();
}
...

这会引起性能问题吗?它甚至是使用ConcurrentBag集合的正确方法吗?

使用ConcurrentBag类型作为List的线程安全替代品

。. NET的内置并发数据结构主要是为像生产者-消费者这样的模式设计的,在这种模式下,容器中有一个恒定的工作流。

在您的示例中,列表似乎是长期(相对于类的生命周期)存储,而不仅仅是在消费者将数据带走并对其进行操作之前的一些数据的休息位置。在这种情况下,我建议使用普通的List<T>(或任何非并发集合最适合您打算的操作),并简单地使用lock来控制对它的访问。

Bag只是集合的最一般形式,允许多个相同的条目,甚至不需要List的排序。在公平不是问题的生产者/消费者环境中,它确实有用,但它不是专门为此设计的。

因为Bag没有任何关于其内容的结构,所以它不太适合执行搜索。特别是,您提到的用例需要的时间与袋子的大小成比例。如果您不需要能够存储一个项目的多个副本,并且您的用例可以接受手动同步,则HashSet可能更好。

据我所知,ConcurrentBag使用了多个列表。它使用ConcurrentBag为每个线程创建一个List。因此,当在同一线程中读取或访问ConcurrentBag时,性能应该与使用普通列表时大致相同,但如果从不同的线程访问ConcurrentBag,则会有性能开销,因为它必须在为每个线程创建的"内部"列表中搜索值。

MSDN页面对ConcurrentBag做了如下说明。

在排序无关紧要的情况下,bag用于存储对象,并且与set不同,bag支持副本。ConcurrentBag是一个线程安全的包实现,针对同一线程将生产和消费存储在包中的数据的场景进行了优化。

http://msdn.microsoft.com/en-us/library/dd381779%28v=VS.100%29.aspx

一般来说,使用ConcurrentBag类型是一个可接受的线程安全的List替代品吗?

不,一般来说不是,因为,我同意Warren Dew的观点,List是有序的,而Bag不是(当然我的不是;)

但是在(可能并发的)读数量大大超过写数量的情况下,您可以只对List进行写时复制(copy-on-write)包装。

这是一个通用的解决方案,因为您正在使用原始List实例,除了(如上面的链接中更详细地解释)您必须确保每个修改List的人都使用适当的写时复制实用程序方法-您可以通过使用List.AsReadOnly()来强制执行。

在高度并发的程序中,与锁相比,写时复制在大多数读场景中具有许多理想的性能属性。