多线程6:使用并发集合

1、简介
(1) ConcurrentQueue
该集合使用了原子的比较和交换(Compare and Swap,简称CAS)操作,以及SpinWait来保证线程安全。
它实现了一个先进先出(First In First Out,简称FIFO)的集合,即元素出入队列的顺序是一致的。
Enqueue方法向队列添加元素,TryDequeue方法视图取出队列中的第一个元素,
TryPeek方法试图获取第一个元素但不从队列中删除该元素。
(2) ConcurrentStack
ConcurrentStack的实现也没有使用任何锁,只采用了CAS操作。
它是一个后进先出(Last In First Out,简称LIFO)的集合,即最后添加的元素会最先返回。
Push和PushRange方法可以添加元素,TryPop和TryPopRange方法可以获取元素,TryPeek方法检查元素。
(3) ConcurrentBag
ConcurrentBag是一个支持重复元素的无序集合。
其工作场景为,多个线程以这样方式工作: 每个线程产生和消费自己的任务,极少与其他线程的任务交互(如果要交互使用锁)。
Add方法添加元素,TryPeek方法检查元素,TryTake方法获取元素。
【注意】避免使用上面提及集合的Count属性。
这些集合使用的是链表,Count操作时间复杂度为O(N),可使用IsEmpty属性检查是否为空,其时间复杂度为O(1)。
(4) ConcurrrentDictionary
ConcurrrentDictionary是一个线程安全的字典集合的实现。
对于读操作无需使用锁。但是对于写操作则需要锁。
该并发字典使用多个锁,在字典桶之上实现了一个细粒度的锁模型。
使用参数concurrencyLevel可以在构造函数中定义锁的数量,这意味着预估的线程数量将并发地更新该字典。
【注意】由于并发字典使用锁,所以一些操作需要获取该字典中的所有锁。如果没有必要要避免使用以下操作:
Count、IsEmpty、Keys、Values、CopyTo及ToArray。
(5) BlockingCollection
BlockingCollection是对IProducerConsumerCollection泛型接口实现的一个高级封装。
当有一些步骤需要使用之前步骤运行的结果时,可使用BlockingCollection来实现管道场景。
BlockingCollection支持以下功能:分块、调整内部集合容量、取消集合操作、从多个块集合中获取元素。

2、使用ConcurrentDictionary
ConcurrentDictionary的写操作比使用锁的Dictionary慢得多,读操作则略快一些。
如果对字典需要大量的线程安全的读操作,ConcurrentDictionary是最好的选择。
如果对字典只需要多线程访问只读元素,那么最好使用通常的字典或ReadOnlyDictionary集合。
ConcurrentDictionary的实现使用了细粒度锁(fine-grained locking)技术,这在多线程写入方面比使用锁的通常字典(也被称为
粗粒度锁)的可伸缩性更好。
当只用一个线程时,并发字典非常慢,但是扩展到五六个线程时(要有足够的CPU核心同时运行它们),并发字典的性能更好。

3、使用ConcurrentQueue实现异步处理
ConcurrentQueue适合于生产-消费模型,能够应对多个线程消费者同时从ConcurrentQueue获取元素的情景。
尽管ConcurrentQueue是先进先出的集合,但由于消费的线程是多个,这样就无法保证元素按照先进先出的顺序被处理。

4、使用ConcurrentStack实现异步处理
和ConcurrentQueue相反,使用ConcurrentStack,早创建的任务具有较低的优先级,
直到生产者停止向堆栈中放入更多的任务后,该任务才可能被处理。

5、使用ConcurrentBag实现异步处理
ConcurrentBag是一个支持重复元素的无序集合,其内部针对多个线程既可以添加元素又可以删除元素的场景进行了优化。
ConcurrentBag准对每个线程使用自己的本地队列的元素,所以使用该队列时无需任何锁。
只有当本地队列中没有任何元素时,才执行一些锁定操作并尝试从其他线程队列中“偷取”工作。
这种行为有助于在所有工作者间分发工作并避免使用锁。

6、使用BlockingCollection进行异步处理
BlockingCollection是对IProducerConsumerCollection泛型接口实现的一个高级封装。
它能够改变任务存储在阻塞集合中的方式。
默认情况下它使用的是ConcurrentQueue容器,但是能使用任何实现了IProducerConsumerCollection泛型接口的集合。
工作者通过对阻塞集合迭代调用GetConsumingEnumerable方法来获取工作项。
如果在该集合中没有任何元素,迭代器会阻塞工作者线程直到有元素被放置到集合中。
当生产者调用集合的CompleteAdding方法时该迭代周期会结束,这标志着工作完成了。
【注意】如果对BlockingCollection本身进行迭代,因为它自身实现了IEnumerable接口,迭代的结果只是集合的“快照”,并非期望的内容。
要使用GetConsumingEnumerable进行迭代,才能获取正确的预期效果。

posted @   xhubobo  阅读(169)  评论(0编辑  收藏  举报
编辑推荐:
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器
点击右上角即可分享
微信分享提示