.NET 4.0 多任务编程 之二 线程安全的集合

随着多核计算机的普及,并行编程技术,也就是多核编程技术也逐渐称为开发的主流。为此,在.NET 4 中就引入了“并行编程”。在.NET 4 中一些列的Library和类为并行编程提供了支持,如:Task Parallel Library,Parallel LINQ等。

在.NET 1.0并行编程技术主要依赖于多线线程技术。多线程最大的问题就是难于使用和管理。在使用多线程的使用,因为它的复杂性,往往使用我们把注意力分散了多线程上。而致使我们的最初目的被掩盖了,比如下面这个例子:
void Add(String x)
{ 
   _collection.Add(x);
}
void List()
{
   foreach(int val in _collection)
     //do something
} 


假如我们使用两个线程,一个进行添加操作,另外一个则进行简单的读操作,很大可能性会抛出一个InvalidOperationException, 因为系统发现集合在读取的同时被修改了。传统的应对方法,就是手工对要操作的对象加锁。即我们可以在对_collection集合进行操作之前对其添加代码lock(_collection)。

然而这种方式毕竟不够简洁,并且在更复杂的情况下它可能会显得非常繁琐,而且容易出错。这时候支持并行操作的集合应运而生了。

Concurrent集合
和Java集合设计的一团混乱相比,.NET将线程安全的集合单独放在System.Collections.Concurrent这个名称空间里,这些类分别存在于两个不同的dll中,其中在System.dll中有
  • BlockingCollection<T>
  • ConcurrentBag<T>

在mscorlib.dll中则稍多一些,他们分别是
  • ConcurrentQueue<T>
  • ConcurrentStack<T>
  • ConcurrentDictionary<TKey, TValue>

首先看看mscorlib中的几个类,这些类和.NET 2.0中普通版本相比,功能基本不变,因此你仍然可以像使用普通的哈希表,队列和堆栈来使用它们。唯一区别比较大的也是在Concurrent命名空间中很常见的各种try函数操作。
在System.dll中的并行集合,这里面的两个类在以前的.NET中是没有的。
首先看看ConcurrentBag<T>,顾名思义,这个类提供并行数据包的功能,这个类相对来说构造比较简单,它继承自四个接口:
  • IProducerConsumerCollection<T>
  • IEnumerable<T>
  • ICollection
  • IEnumerable

IProducerConsumerCollection<T> 是个新的接口,这个接口提供了生产者/消费者的集合操作,它提供了四个基本的方法:CopyTo(T[],int), ToArray(), TryAdd(T), TryTake(out T)。其中我们主要关注后面的两个方法,TryAdd是添加元素操作,而TryTake则是取元素操作。
不过在ConcurrentBag中,TryAdd方法被设置为protected,外部对象需要通过Add操作来添加元素。另外,取元素的话,除了TryTake之外,我们还可以通过TryPeek来取集合当前的最后一个元素而不删除它。上面的例子经过简单修改之后,就可以避免线程冲突的错误
static void main(){
    ConcurrentBag<string> bag = new ConcurrentBag<string>();
    Task.Factory.StartNew(() =>
    {
        for (int i = 0; i < 1000; i++)
        {
            bag.Add(i.ToString());
        }
        bag.Add("Last");
    });
    Task.Factory.StartNew(() =>
    {
        foreach (string item in bag)
        {
            Console.WriteLine(item);
        }
    }).Wait();} 

BlockingCollection<T>相对来说,要稍微复杂一些,它也提供了比ConcurrentBag更加丰富的功能。如CompleteAdding和超时设置的TryAdd和TryTake方法等等。

新特性
并行类同时还提供了一些更丰富的功能,由于Lamda表达式的引入,现在在ConcurrentDictionary<TKey, TValue>你可以通过AddOrUpdate或GetOrAdd添加自己的值生成方案。这使得我们在生成键值对的时候更加方便和简单了。如:
ConcurrentDictionary<int, string> td = new ConcurrentDictionary<int, string>();
Func<int, string> genVar = (i) => i.ToString();
Task.Factory.StartNew(() =>{
    for (int i = 0; i < 1000; i++)    {
        td.GetOrAdd(i, genVar);    }});

Task.Factory.StartNew(() =>{
    Func<int, string, string> updateVar = (key, oldVar) => oldVar + key;
    td.AddOrUpdate(0, genVar, updateVar);
    Console.WriteLine(td[0]);}).Wait(); 




如果读取共享状态比写入该状态花的时间要长得多(这是常有的情况),读/写锁就被广泛使用以提高可扩展性。


参考:
http://www.bluebytesoftware.com/blog/2009/01/30/ASinglewordReaderwriterSpinLock.aspx

你可能感兴趣的:(多线程,编程,.net,Blog,LINQ)