代码改变世界

.Net 4.0 Parallel 编程(九)Task中的数据共享(下)

2011-04-21 10:17  Henry Cui  阅读(2976)  评论(6编辑  收藏  举报

在上篇Post中我们看过了几种常用的同步类型,本篇文章会介绍下申明性的同步的实现以及对于集合类型数据共享的问题,首先看下申明性同步的实现。

申明性同步

我们可以通过使用Synchronization 特性来标识一个类,从而使一个类型的字段以及方法都实现同步化。在使用Synchronization 时,我们需要将我们的目标同步的类继承于System.ContextBoundObject类型。我们来看看之前的例子我们同步标识Synchronization 的实现:

[Synchronization]
class SumClass : ContextBoundObject
{
    private int _Sum;

    public void Increment()
    {
        _Sum++;
    }

    public int GetSum()
    {
        return _Sum;
    }
}
class Program
{
    static void Main(string[] args)
    {
        var sum = new SumClass();
        Task[] tasks = new Task[10];
        for (int i = 0; i < 10; i++)
        {
            tasks[i] = new Task(() =>
            {
                for (int j = 0; j < 1000; j++)
                {
                    sum.Increment();
                }
            });
            tasks[i].Start();
        }
        Task.WaitAll(tasks);
        Console.WriteLine("Expected value {0}, Parallel value: {1}",
        10000, sum.GetSum());
        Console.ReadLine();
    }
}

并发集合

在某些时候我们需要并发的操作集合类型,对于集合类型也存在数据共享的问题,我们来看一个例子,在例子中我们创建一个队列,然后多个Task同步进行出队列操作,看会不会出现同步出一个元素的情况,其中我们通过一个计数器进行统计出队列的次数:

static void Main(string[] args)
{
    for (var j = 0; j < 10; j++)
    {
        var queue = new Queue<int>();
        var count = 0;
        for (var i = 0; i < 1000; i++)
        {
            queue.Enqueue(i);
        }
        var tasks = new Task[10];
        for (var i = 0; i < tasks.Length; i++)
        {
            tasks[i] = new Task(() =>
            {
                while (queue.Count > 0)
                {
                    var item = queue.Dequeue();
                    Interlocked.Increment(ref count);
                }
            });
            tasks[i].Start();
        }
        try
        {
            Task.WaitAll(tasks);
        }
        catch (AggregateException e)
        {
            e.Handle((ex) =>
            {
                Console.WriteLine("Exception Message:{0}",ex.Message);
                return true;
            });
        }
        Console.WriteLine("Dequeue items count :{0}", count);
    }
    Console.ReadKey();
}

在上面的示例中,我们为了让效果更佳明显,看十次运行的结果:

image

在上面我们看到两个问题:1.出队列的次数超过了1000个;2.出现异常消息已经为空。出现这两个异常的原因是:1.多个线程同时在操作一个元素进行出的动作;2消息为空是因为当队列中剩下最后一个元素时,某个线程在访问“queue.Count > 0”时确实是大于0的,所以进入循环代码中准备进行出队列动作,而另外一个线程也进入循环中,已经将队列中出完了,就出现了数据争用的情况。

在.Net 4.0中提供了很多并发的集合类型来让我们处理数据同步的集合的问题,这里面包括:

1.ConcurrentQueue:提供并发安全的队列集合,以先进先出的方式进行操作;

2.ConcurrentStack:提供并发安全的堆栈集合,以先进后出的方式进行操作;

3.ConcurrentBag:提供并发安全的一种特殊的排序的集合;

4.ConcurrentDictionary:提供并发安全的一种key-value类型的集合。

我们在这里只做ConcurrentQueue的一个尝试,并发队列是一种线程安全的队列集合,我们可以通过Enqueue()进行排队、TryDequeue()进行出队列操作,在上面的例子中我们使用ConcurrentQueue时:

static void Main(string[] args)
{
    for (var j = 0; j < 10; j++)
    {
        var queue = new ConcurrentQueue<int>();
        var count = 0;
        for (var i = 0; i < 1000; i++)
        {
            queue.Enqueue(i);
        }
        var tasks = new Task[10];
        for (var i = 0; i < tasks.Length; i++)
        {
            tasks[i] = new Task(() =>
            {
                while (queue.Count > 0)
                {
                    int item;
                    var isDequeue = queue.TryDequeue(out item);
                    if(isDequeue) Interlocked.Increment(ref count);
                }
            });
            tasks[i].Start();
        }
        try
        {
            Task.WaitAll(tasks);
        }
        catch (AggregateException e)
        {
            e.Handle((ex) =>
            {
                Console.WriteLine("Exception Message:{0}",ex.Message);
                return true;
            });
        }
        Console.WriteLine("Dequeue items count :{0}", count);
    }
    Console.ReadKey();
}

总结

在本文中看过了数据共享中如何使用Synchronization 进行申明性同步,以及同步集合的一些介绍,希望对您有帮助。到此,Task中的数据共享就介绍结束了,下面我们看下PLinq中的内容。

alt
作者:Henry Cui
出处: http://henllyee.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明。