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> 当前数据量为:3000000
List<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
“`
得出结果