何时使用线程安全集合

.NET Framework 4 引入了五种专为支持多线程添加和删除作而设计的集合类型。 为了实现线程安全,这些类型使用各种高效的锁定和无锁同步机制。 同步操作会增加操作的开销。 开销取决于所使用的同步类型、所执行的操作类型以及其他因素,例如尝试同时访问集合的线程数。

在某些情况下,同步开销可以忽略不计,使多线程类型在受外部锁保护时,其性能显著加快,并且其可扩展性显著优于非线程安全的等效项。 在其他方案中,开销可能会导致线程安全类型的执行速度和缩放水平与该类型外部锁定的非线程安全版本相同,甚至更差。

以下部分提供了一般指导,说明何时应该使用线程安全集合,以及何时应该使用用户在其读写操作上提供锁的非线程安全等效集合。 由于性能可能因许多因素而异,因此指导并不具体,并且在所有情况下都不一定有效。 如果性能非常重要,则确定要使用的集合类型的最佳方法是基于具有代表性的计算机配置和负载来衡量性能。 本文档使用以下术语:

纯生产者-消费者方案
任何给定线程都是添加或删除元素,但不是两者。

混合生成者-使用者方案
任何给定线程都添加和删除元素。

加速
相对于同一方案中另一种类型的算法性能更快。

可伸缩性
与计算机上的内核数成正比的性能增加。 在八个核心上运行的可扩展算法比在两个核心上运行时速度更快。

ConcurrentQueue (T) 与 Queue(T)

在纯生产者-消费者场景中,当每个元素的处理时间非常短(仅需要一些指令)时,与具有外部锁的 System.Collections.Concurrent.ConcurrentQueue<T> 相比,System.Collections.Generic.Queue<T> 可以提供适度的性能优势。 在此方案中,ConcurrentQueue<T> 的性能最佳表现是在一个专用线程负责排队而另一个专用线程负责取消排队的情况下。 如果不强制实施此规则,则 Queue<T> 甚至可能比具有多个核心的计算机执行速度略快 ConcurrentQueue<T>

当处理时间约为 500 FLOPS(浮点运算)或更多时,ConcurrentQueue<T> 的双线程规则不适用,从而具有非常好的可伸缩性。 Queue<T> 在此情况下无法正常伸缩。

在混合生产者-消费者场景中,当处理时间非常短时,带有外部锁的Queue<T>ConcurrentQueue<T>具有更好的扩展性。 但是,处理时间大约为 500 FLOPS 或更长时,ConcurrentQueue<T> 的伸缩性更佳。

ConcurrentStack 与堆栈

在纯制造者-使用者方案中,当处理时间非常短时,System.Collections.Concurrent.ConcurrentStack<T> 和带外部锁的 System.Collections.Generic.Stack<T> 在使用一个专用推送线程和一个专用弹出线程时的执行性能可能大致相同。 但是,随着线程数的增加,这两种类型由于争用的增加而减慢,并且 Stack<T> 性能可能优于 ConcurrentStack<T>。 当处理时间大约为 500 FLOPS 或更多时,这两种类型按大约相同的速率进行缩放。

在混合生成者-使用者方案中,对于小型和大型工作负荷而言, ConcurrentStack<T> 速度更快。

使用 PushRangeTryPopRange 可能大大加快访问时间。

ConcurrentDictionary 与词典

一般情况下,在需要从多个线程同时添加或更新键或值的任何场景中,请使用System.Collections.Concurrent.ConcurrentDictionary<TKey,TValue>。 在涉及频繁更新和相对较少读取的场景中,ConcurrentDictionary<TKey,TValue> 通常会提供适度的好处。 在涉及许多读取和许多更新的方案中, ConcurrentDictionary<TKey,TValue> 通常具有任意数目核心的计算机上的速度要快得多。

在涉及频繁更新的方案中,可以增加并发 ConcurrentDictionary<TKey,TValue> 程度,然后测量性能是否在具有更多核心的计算机上增加。 如果更改并发级别,请尽可能避免全局操作。

如果您只是读取键或值,Dictionary<TKey,TValue> 会更快,因为如果没有线程修改字典,则不需要同步。

ConcurrentBag

在纯生成者-使用者方案中, System.Collections.Concurrent.ConcurrentBag<T> 性能可能比其他并发收集类型慢。

在混合生成者-使用者方案中,对于大型和小型工作负荷, ConcurrentBag<T> 通常比任何其他并发收集类型更快、更具可伸缩性。

BlockingCollection

需要限制和阻止语义时,System.Collections.Concurrent.BlockingCollection<T> 的执行速度可能优于任何自定义实现。 它还支持丰富的取消、枚举和异常处理。

另请参阅