vlambda博客
学习文章列表

C# .net 集合-并发处理(List集合换成ConcurrentQueue、ConcurrentDictionary )

背景 
List集合,数组Int[],String[] ……,Dictory字典等等。但是这些列表、集合和数组的线程都不是安全的,不能接受并发请求。例如:

namespace Spider{ class Program {
private static List<Product> _Products { get; set; } static void Main(string[] args) {
_Products = new List<Product>(); Task t1 = Task.Factory.StartNew(() => { AddProducts(); }); Task t2 = Task.Factory.StartNew(() => { AddProducts(); }); Task t3 = Task.Factory.StartNew(() => { AddProducts(); });
Task.WaitAll(t1, t2, t3);//同步执行 Console.WriteLine(_Products.Count); Console.ReadLine(); } static void AddProducts() { Parallel.For(0, 1000, (i) => { Product product = new Product(); product.Name = "name" + i; product.Category = "Category" + i; product.SellPrice = i; _Products.Add(product); });
} } class Product { public string Name { get; set; } public string Category { get; set; } public int SellPrice { get; set; } }}

上图理论上是会显示3000行数据,但是实际上显示了2934个由于是list集合并不能保证线程安全,所以导致数据丢失,无法保证数据的一致性。这个时候我们常用的解决方法就是加锁(lock)lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。

static void Main(string[] args) {
_Products = new List<Product>(); Stopwatch swTask = new Stopwatch();//用于统计时间消耗的 swTask.Start(); Task t1 = Task.Factory.StartNew(() => { AddProducts(); }); Task t2 = Task.Factory.StartNew(() => { AddProducts(); }); Task t3 = Task.Factory.StartNew(() => { AddProducts(); }); Task.WaitAll(t1, t2, t3);//同步执行 swTask.Stop(); Console.WriteLine("List<Product> 当前数据量为:" + _Products.Count); Console.WriteLine("List<Product> 执行时间为:" + swTask.ElapsedMilliseconds); Console.ReadLine(); } static void AddProducts() { Parallel.For(0, 1000000, (i) => { lock (_Products) {
Product product = new Product(); product.Name = "name" + i; product.Category = "Category" + i; product.SellPrice = i; _Products.Add(product); } //lock 是Monitor的语法糖 //Monitor.Enter(_Products); //Product product = new Product(); //product.Name = "name" + i; //product.Category = "Category" + i; //product.SellPrice = i; //_Products.Add(product); //Monitor.Exit(_Products); });
}
List<Product> 当前数据量为:3000000List<Product> 执行时间为:4638

这个时候就显示3000

但是锁的引入,带来了一定的开销和性能的损耗,并降低了程序的扩展性,而且还会有死锁的发生(虽说概率不大,但也不能不防啊),因此:使用LOCK进行并发编程显然不太适用。

还好,微软一直在更新自己的东西:

.NET Framework 4提供了新的线程安全和扩展的并发集合,它们能够解决潜在的死锁问题和竞争条件问题,因此在很多复杂的情形下它们能够使得并行代码更容易编写,这些集合尽可能减少使用锁的次数,从而使得在大部分情形下能够优化为最佳性能,不会产生不必要的同步开销。

需要注意的是:在串行代码中使用并发集合是没有意义的,因为它们会增加无谓的开销。

在.NET Framework4.0以后的版本中提供了命名空间:System.Collections.Concurrent 来解决线程安全问题,通过这个命名空间,能访问以下为并发做好了准备的集合。

1.BlockingCollection 与经典的阻塞队列数据结构类似,能够适用于多个任务添加和删除数据,提供阻塞和限界能力。

2.ConcurrentBag 提供对象的线程安全的无序集合

3.ConcurrentDictionary 提供可有多个线程同时访问的键值对的线程安全集合

4.ConcurrentQueue 提供线程安全的先进先出集合

5.ConcurrentStack 提供线程安全的后进先出集合

这些集合通过使用比较并交换和内存屏障等技术,避免使用典型的互斥重量级的锁,从而保证线程安全和性能。

static void Main(string[] args) {
_Products = new ConcurrentQueue<Product>(); Stopwatch swTask = new Stopwatch();//用于统计时间消耗的 swTask.Start(); Task t1 = Task.Factory.StartNew(() => { AddProducts(); }); Task t2 = Task.Factory.StartNew(() => { AddProducts(); }); Task t3 = Task.Factory.StartNew(() => { AddProducts(); }); Task.WaitAll(t1, t2, t3);//同步执行 swTask.Stop(); Console.WriteLine("List<Product> 当前数据量为:" + _Products.Count); Console.WriteLine("List<Product> 执行时间为:" + swTask.ElapsedMilliseconds); Console.ReadLine(); } static void AddProducts() { Parallel.For(0, 1000000, (i) => {
Product product = new Product(); product.Name = "name" + i; product.Category = "Category" + i; product.SellPrice = i; _Products.Enqueue(product);
});
} }

List 当前数据量为:3000000 
List 执行时间为:2911
“` 
得出结果