隐藏

在C#多线程开发中使用并发集合

发布:2022/12/26 15:56:26作者:管理员 来源:本站 浏览次数:350

在C#语言中当需要处理并发的场景时,就需要程序员使用最合理的数据结构。那么哪些数据结构是支持和可以在并行计算中被使用的呢。


首先这些数据结构具备可伸缩性,尽可能地避免锁(会造成多个线程的等待,防止资源竞争),同时还能提供线程安全的访问。


在.NET Framework4.0中引入了System.Collections.Concurrent命名空间,其中就包含几个数据结构。


   ConcurrentQueueConcurrentDictionaryConcurrentStackConcurrentBagBlockingCollect


那么接下来,我们来看看这些支持并行计算的数据结构到底应该如何使用!

ConcurrentQueue


该集合使用了原子的比较和交换(CAS),以及SpinWait来保持线程安全。


它实现了一个先进先出(First In First Out简称FIFO)的集合。元素的出队列和加入队列的顺序是一致的。


Enqueue(): 向队列中加入元素。


TryDequeue(): 取出队列中的第一个元素,此时队里无此元素。


TryPeek(): 得到第一个元素但并不从队列中删除该元素。

ConcurrentStack


此集合在实现层也没有加入任何锁,只采用了CAS操作。


它是一个后进后出(Last In First Out,简称LIFO)集合,意为着最近添加的元素先出去(类比为栈)。


Push()和PushRange(): 给该集合添加元素。


TryPop()和TryPopRange(): 从该集合获取元素。


TryPeek(): 检查元素。

ConcurrentBag


该集合是一个支持重复元素的无序集合,专门针对下面多个线程工作时,集合进行了优化。每个线程产生和消费自己的任务,极少与其他线程的任何交互(若需要使用交互,则必须使用锁操作)。


Add(): 添加元素。


TryPeek(): 检查元素。


TryTake(): 获取元素。

ConcurrentDictionary


是一个线程安全的字典集合。其中读操作无需使用锁,写操作需要使用锁。


该并发字典使用多个锁,在字典之上实现一个细粒度的锁模型。其中使用concurrencyLevel可以在构造函数中定义锁的数量,那么说意味着预估的线程数量将并发地更新该字典。


   由于并发字典使用锁,所以一些操作需要获取该字典中的所有锁。若是在编程过程中,没有必要则不要调用下面方法:Count,IsEmpty,Keys,Values,CopyTo及ToArray。


BlockingCollection


该集合是对泛型接口IProducerConsumerCollection实现的一个高级封装。其中有很多管道场景,即当你有一些操作需要使用之前计算的结果。


BlockingCollection支持如下功能:


   分块调整内部集合容量取消集合操作从多个块集合中获取元素


Demo


在单线程的环境中使用通用字典与使用通用字典的性能。

使用ConcurrentDictionary


   class Program

   {

       const string Item = "";

       public static string CurrentItem;

       static void Main(string[] args)

       {

           var concurrentDicrionary=new ConcurrentDictionary<int ,string>();

           var dictionary=new Dictionary<int ,string>();


           var sw = new Stopwatch();

           sw.Start();

           for (int i = 0; i < 1000000; i++)

           {

               lock(dictionary)

               {

                   dictionary[i]=Item;

               }

           }

           sw.Stop();

           Console.WriteLine("写dictionary的时间"+sw.Elapsed);


           sw.Restart();

           for (int i = 0; i < 1000000; i++)

           {

               concurrentDicrionary[i] = Item;

           }

           sw.Stop();

           Console.WriteLine("写并发集合concurrentDicrionary的时间:" + sw.Elapsed);


           sw.Restart();

           for (int i = 0; i < 1000000; i++)

           {

               lock(dictionary)

               {

                   CurrentItem = dictionary[i];

               }

           }

           sw.Stop();

           Console.WriteLine("读dictionary的时间" + sw.Elapsed);


           sw.Restart();

           for (int i = 0; i < 1000000; i++)

           {

               CurrentItem=concurrentDicrionary[i];

           }

           sw.Stop();

           Console.WriteLine("读并发集合concurrentDicrionary的时间:" + sw.Elapsed);


           Console.ReadKey();

       }

   }


可以发现使用ConcurrentDictionary写操作比使用锁的通用字典要慢很多,而读操作则更快些。因此如果对字典需要大量的线程安全的读操作,则ConcurrentDictionary是更好的选择。