C# 并发集合

原创 2016年08月29日 18:00:13
基于任务的程序设计、命令式数据并行和任务并行都要求能够支持并发更新的数组、列表和集合。

在.NET Framework 4 以前,为了让共享的数组、列表和集合能够被多个线程更新,需要添加复杂的代码来同步这些更新操作。

如您需要编写一个并行循环,这个循环以无序的方式向一个共享集合中添加元素,那么必须加入一个同步机制来保证这是一个线程安全的集合。

一、System.Collenctions和System.Collenctions.Generic 名称空间中所提供的经典列表、集合和数组的线程都不是安全的,不能接受并发请求,因此需要对相应的操作方法执行串行化。

下面代码中并没有实现线程安全和串行化:

 class Program
    {
        private static object o = new object();
        private static List<Product> _Products { get; set; } 
        / *  代码中 创建三个并发线程 来操作_Products 集合
         *  System.Collections.Generic.List 这个列表在多个线程访问下,不能保证是安全的线程,所以不能接受并发的请求,我们必须对ADD方法的执行进行串行化
         */
        static void Main(string[] args)
        {
            _Products = new List<Product>();
            /*创建任务 t1  t1 执行 数据集合添加操作*/
            Task t1 = Task.Factory.StartNew(() =>
            {
                AddProducts();
            });
            /*创建任务 t2  t2 执行 数据集合添加操作*/
            Task t2 = Task.Factory.StartNew(() =>
            {
                AddProducts();
            });
            /*创建任务 t3  t3 执行 数据集合添加操作*/
            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; }
    }
代码中开启了三个并发操作,每个操作都向集合中添加1000条数据,在没有保障线程安全和串行化的运行下,实际得到的数据并没有3000条,结果如下

为此我们需要采用Lock关键字,来确保每次只有一个线程来访问 _Products.Add(product); 这个方法,代码如下:

class Program
    {
        private static object o = new object();
        private static List<Product> _Products { get; set; }
         /*  代码中 创建三个并发线程 来操作_Products 集合
         *  System.Collections.Generic.List 这个列表在多个线程访问下,不能保证是安全的线程,所以不能接受并发的请求,我们必须对ADD方法的执行进行串行化
         */
        static void Main(string[] args)
        {
            _Products = new List<Product>();
            /*创建任务 t1  t1 执行 数据集合添加操作*/
            Task t1 = Task.Factory.StartNew(() =>
            {
                AddProducts();
            });
            /*创建任务 t2  t2 执行 数据集合添加操作*/
            Task t2 = Task.Factory.StartNew(() =>
            {
                AddProducts();
            });
            /*创建任务 t3  t3 执行 数据集合添加操作*/
            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;
                lock (o)
                {
                    _Products.Add(product);
                }
            });

        }
    }

    class Product
    {
        public string Name { get; set; }
        public string Category { get; set; }
        public int SellPrice { get; set; }
    }

但是锁的引入,带来了一定的开销和性能的损耗,并降低了程序的扩展性,在并发编程中显然不适用。

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

线程安全并不是没有代价的,比起System.Collenctions和System.Collenctions.Generic命名空间中的列表、集合和数组来说,并发集合会有更大的开销。因此,应该只在需要从多个任务中并发访问集合的时候才使用并发几个,在串行代码中使用并发集合是没有意义的,因为它们会增加无谓的开销。

为此,在.NET Framework中提供了System.Collections.Concurrent新的命名空间可以访问用于解决线程安全问题,通过这个命名空间能访问以下为并发做好了准备的集合。

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

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

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

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

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

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

1.ConcurrentQueue

ConcurrentQueue 是完全无锁的,能够支持并发的添加元素,先进先出。下面贴代码,详解见注释:

class Program
    {
        private static object o = new object();
        /*定义 Queue*/
        private static Queue<Product> _Products { get; set; }
        private static ConcurrentQueue<Product> _ConcurrenProducts { get; set; } 
         /*  代码中 创建三个并发线程 来操作_Products 和 _ConcurrenProducts 集合,每次添加 10000 条数据 查看 一般队列Queue 和 多线程安全下的队列ConcurrentQueue 执行情况
         */
        static void Main(string[] args)
        {
            Thread.Sleep(1000);
            _Products = new Queue<Product>();
            Stopwatch swTask = new Stopwatch();
            swTask.Start();

            /*创建任务 t1  t1 执行 数据集合添加操作*/
            Task t1 = Task.Factory.StartNew(() =>
            {
                AddProducts();
            });
            /*创建任务 t2  t2 执行 数据集合添加操作*/
            Task t2 = Task.Factory.StartNew(() =>
            {
                AddProducts();
            });
            /*创建任务 t3  t3 执行 数据集合添加操作*/
            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);

            Thread.Sleep(1000);
            _ConcurrenProducts = new ConcurrentQueue<Product>();
            Stopwatch swTask1 = new Stopwatch();
            swTask1.Start();

            /*创建任务 tk1  tk1 执行 数据集合添加操作*/
            Task tk1 = Task.Factory.StartNew(() =>
            {
                AddConcurrenProducts();
            });
            /*创建任务 tk2  tk2 执行 数据集合添加操作*/
            Task tk2 = Task.Factory.StartNew(() =>
            {
                AddConcurrenProducts();
            });
            /*创建任务 tk3  tk3 执行 数据集合添加操作*/
            Task tk3 = Task.Factory.StartNew(() =>
            {
                AddConcurrenProducts();
            });

            Task.WaitAll(tk1, tk2, tk3);
            swTask1.Stop();
            Console.WriteLine("ConcurrentQueue<Product> 当前数据量为:" + _ConcurrenProducts.Count);
            Console.WriteLine("ConcurrentQueue<Product> 执行时间为:" + swTask1.ElapsedMilliseconds);
            Console.ReadLine();
        }

        /*执行集合数据添加操作*/
        static void AddProducts()
        {
            Parallel.For(0, 30000, (i) =>
            {
                Product product = new Product();
                product.Name = "name" + i;
                product.Category = "Category" + i;
                product.SellPrice = i;
                lock (o)
                {
                    _Products.Enqueue(product);
                }
            });

        }
        /*执行集合数据添加操作*/
        static void AddConcurrenProducts()
        {
            Parallel.For(0, 30000, (i) =>
            {
                Product product = new Product();
                product.Name = "name" + i;
                product.Category = "Category" + i;
                product.SellPrice = i;
                _ConcurrenProducts.Enqueue(product);
            });

        }
    }

    class Product
    {
        public string Name { get; set; }
        public string Category { get; set; }
        public int SellPrice { get; set; }
    }

需要注意的是,代码中的输出时间并不能够完全正确的展示出并发代码下的ConcurrentQueue性能,采用ConcurrentQueue在一定程度上也带来了损耗,如下图所示:

ConcurrentQueue 还有另外两种方法:TryDequeue 尝试移除并返回 和 TryPeek 尝试返回但不移除,下面贴代码:

class Program
    {
        private static object o = new object();
        private static ConcurrentQueue<Product> _ConcurrenProducts { get; set; }
        /*
         *  ConcurrentQueue  下的 TryPeek 和 TryDequeue
         */
        static void Main(string[] args)
        {
            _ConcurrenProducts = new ConcurrentQueue<Product>();
            /*执行添加操作*/
            Console.WriteLine("执行添加操作");
            Parallel.Invoke(AddConcurrenProducts, AddConcurrenProducts);
            Console.WriteLine("ConcurrentQueue<Product> 当前数据量为:" + _ConcurrenProducts.Count);
            /*执行TryPeek操作   尝试返回不移除*/
            Console.WriteLine("执行TryPeek操作   尝试返回不移除");
            Parallel.Invoke(PeekConcurrenProducts, PeekConcurrenProducts);
            Console.WriteLine("ConcurrentQueue<Product> 当前数据量为:" + _ConcurrenProducts.Count);

            /*执行TryDequeue操作  尝试返回并移除*/
            Console.WriteLine("执行TryDequeue操作  尝试返回并移除");
            Parallel.Invoke(DequeueConcurrenProducts, DequeueConcurrenProducts);
            Console.WriteLine("ConcurrentQueue<Product> 当前数据量为:" + _ConcurrenProducts.Count);

            Console.ReadLine();
        }

        /*执行集合数据添加操作*/
        static void AddConcurrenProducts()
        {
            Parallel.For(0, 100, (i) =>
            {
                Product product = new Product();
                product.Name = "name" + i;
                product.Category = "Category" + i;
                product.SellPrice = i;
                _ConcurrenProducts.Enqueue(product);
            });
        }
        /*尝试返回 但不移除*/
        static void PeekConcurrenProducts()
        {
            Parallel.For(0, 2, (i) =>
            {
                Product product = null;
                bool excute = _ConcurrenProducts.TryPeek(out product);
                Console.WriteLine(product.Name);
            });
        }
        /*尝试返回 并 移除*/
        static void DequeueConcurrenProducts()
        {
            Parallel.For(0, 2, (i) =>
            {
                Product product = null;
                bool excute = _ConcurrenProducts.TryDequeue(out product);
                Console.WriteLine(product.Name);
            });
        }
    }

    class Product
    {
        public string Name { get; set; }
        public string Category { get; set; }
        public int SellPrice { get; set; }
    }
需要注意 TryDequeue 和 TryPeek 的无序性,在多线程下


2.ConcurrentStack 

ConcurrentStack 是完全无锁的,能够支持并发的添加元素,后进先出。下面贴代码,详解见注释:

private static object o = new object();
        /*定义 Stack*/
        private static Stack<Product> _Products { get; set; }
        private static ConcurrentStack<Product> _ConcurrenProducts { get; set; }
        /* 
         *  代码中 创建三个并发线程 来操作_Products 和 _ConcurrenProducts 集合,每次添加 30000 条数据 查看 一般Stack 和 多线程安全下的 ConcurrentStack 执行情况
         */
        static void Main(string[] args)
        {
            Thread.Sleep(1000);
            _Products = new Stack<Product>();
            Stopwatch swTask = new Stopwatch();
            swTask.Start();

            /*创建任务 t1  t1 执行 数据集合添加操作*/
            Task t1 = Task.Factory.StartNew(() =>
            {
                AddProducts();
            });
            /*创建任务 t2  t2 执行 数据集合添加操作*/
            Task t2 = Task.Factory.StartNew(() =>
            {
                AddProducts();
            });
            /*创建任务 t3  t3 执行 数据集合添加操作*/
            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);

            Thread.Sleep(1000);
            _ConcurrenProducts = new ConcurrentStack<Product>();
            Stopwatch swTask1 = new Stopwatch();
            swTask1.Start();

            /*创建任务 tk1  tk1 执行 数据集合添加操作*/
            Task tk1 = Task.Factory.StartNew(() =>
            {
                AddConcurrenProducts();
            });
            /*创建任务 tk2  tk2 执行 数据集合添加操作*/
            Task tk2 = Task.Factory.StartNew(() =>
            {
                AddConcurrenProducts();
            });
            /*创建任务 tk3  tk3 执行 数据集合添加操作*/
            Task tk3 = Task.Factory.StartNew(() =>
            {
                AddConcurrenProducts();
            });

            Task.WaitAll(tk1, tk2, tk3);
            swTask1.Stop();
            Console.WriteLine("ConcurrentStack<Product> 当前数据量为:" + _ConcurrenProducts.Count);
            Console.WriteLine("ConcurrentStack<Product> 执行时间为:" + swTask1.ElapsedMilliseconds);
            Console.ReadLine();
        }

        /*执行集合数据添加操作*/
        static void AddProducts()
        {
            Parallel.For(0, 30000, (i) =>
            {
                Product product = new Product();
                product.Name = "name" + i;
                product.Category = "Category" + i;
                product.SellPrice = i;
                lock (o)
                {
                    _Products.Push(product);
                }
            });

        }
        /*执行集合数据添加操作*/
        static void AddConcurrenProducts()
        {
            Parallel.For(0, 30000, (i) =>
            {
                Product product = new Product();
                product.Name = "name" + i;
                product.Category = "Category" + i;
                product.SellPrice = i;
                _ConcurrenProducts.Push(product);
            });

        }
    }

    class Product
    {
        public string Name { get; set; }
        public string Category { get; set; }
        public int SellPrice { get; set; }
    }


ConcurrentStack 还有另外两种方法:TryPop 尝试移除并返回 和 TryPeek 尝试返回但不移除,下面贴代码:

class Program
    {
        private static object o = new object();
        private static ConcurrentStack<Product> _ConcurrenProducts { get; set; }
        /* 
         *  ConcurrentQueue  下的 TryPeek 和 TryPop
         */
        static void Main(string[] args)
        {
            _ConcurrenProducts = new ConcurrentStack<Product>();
            /*执行添加操作*/
            Console.WriteLine("执行添加操作");
            Parallel.Invoke(AddConcurrenProducts, AddConcurrenProducts);
            Console.WriteLine("ConcurrentStack<Product> 当前数据量为:" + _ConcurrenProducts.Count);
            /*执行TryPeek操作   尝试返回不移除*/
            Console.WriteLine("执行TryPeek操作   尝试返回不移除");
            Parallel.Invoke(PeekConcurrenProducts, PeekConcurrenProducts);
            Console.WriteLine("ConcurrentStack<Product> 当前数据量为:" + _ConcurrenProducts.Count);

            /*执行TryDequeue操作  尝试返回并移除*/
            Console.WriteLine("执行TryPop操作  尝试返回并移除");
            Parallel.Invoke(PopConcurrenProducts, PopConcurrenProducts);
            Console.WriteLine("ConcurrentStack<Product> 当前数据量为:" + _ConcurrenProducts.Count);

            Console.ReadLine();
        }

        /*执行集合数据添加操作*/
        static void AddConcurrenProducts()
        {
            Parallel.For(0, 100, (i) =>
            {
                Product product = new Product();
                product.Name = "name" + i;
                product.Category = "Category" + i;
                product.SellPrice = i;
                _ConcurrenProducts.Push(product);
            });
        }
        /*尝试返回 但不移除*/
        static void PeekConcurrenProducts()
        {
            Parallel.For(0, 2, (i) =>
            {
                Product product = null;
                bool excute = _ConcurrenProducts.TryPeek(out product);
                Console.WriteLine(product.Name);
            });
        }
        /*尝试返回 并 移除*/
        static void PopConcurrenProducts()
        {
            Parallel.For(0, 2, (i) =>
            {
                Product product = null;
                bool excute = _ConcurrenProducts.TryPop(out product);
                Console.WriteLine(product.Name);
            });
        }
    }

    class Product
    {
        public string Name { get; set; }
        public string Category { get; set; }
        public int SellPrice { get; set; }
    }


http://msdn.microsoft.com/zh-cn/library/system.collections.concurrent(v=vs.110).aspx

版权声明:本文为博主原创文章,未经博主允许不得转载。

C# 并行编程 之 并发集合 (.Net Framework 4.0)

此文为个人学习《C#并行编程高级教程》的笔记,总结并调试了一些文章中的代码示例。 在以后开发过程中可以加以运用。 对于并行任务,与其相关紧密的就是对一些共享资源,数据结构的并行访问。经常要做的就是...

【C#】58. .Net中的并发集合——BlockingCollection

这篇是并发集合中的最后一篇,介绍一下BlockingCollection。在工作中我还没有使用过,但是看上去应该是为了便捷使用并发集合而创建的类型。默认情况下,BlockingCollection使用...

C# 集合-并发处理-锁OR线程

每次写博客,第一句话都是这样的:程序员很苦逼,除了会写程序,还得会写博客!当然,希望将来的一天,某位老板看到此博客,给你的程序员职工加点薪资吧!因为程序员的世界除了苦逼就是沉默。我眼中的程序员大多都不...
  • singgel
  • singgel
  • 2017年01月04日 15:56
  • 463

C# 集合-并发处理-锁OR线程

C# 集合-并发处理-锁OR线程 2016-08-23 16:02 by 天才卧龙, 8067 阅读, 2 评论, 收藏, 编辑    每次写博客,第一句话都是这样的:程序员很苦逼,除了会...

C#并行编程-并发集合 - 释迦苦僧 - 博客园

基于任务的程序设计、命令式数据并行和任务并行都要求能够支持并发更新的数组、列表和集合。 在.NET Framework 4 以前,为了让共享的数组、列表和集合能够被多个线程更新,需要添加复杂的代...

【C#】55. .Net中的并发集合——ConcurrentQueue

这部分内容我觉得可以用在网络爬虫之类的任务上,应该是很有启发的。 客户类型: class Custom { public int Id { get; set; } }“随机等待时间”任务: st...

【C#】57. .Net中的并发集合——ConcurrentBag

这个是一个直接的爬虫可伸缩应用,虽然大致看明白了,但是真的用到实际中,如何处理超时和网络连接失败呢? Crawling类型:用于标示需要爬取的网页的URL,以及是由哪个爬虫找到的。 cla...

【C#】54. .Net中的并发集合——ConcurrentDictionary

转载自《C#多线程编程实例》 对于并行计算,我们需要使用适当的数据结构。这些结构具备可伸缩性,尽可能地避免锁,并且还能提供线程安全的访问。.Net Framework 引入了System.Colle...

(C#)使用队列(Queue)解决简单的并发问题

有一个场景:一个抢购的项目,假设有5件商品,谁先抢到谁可以买,但是如果此时此刻(这里的此时此刻假设是相同的时间),有100人去抢这个商品,如果使用平时的方法会出现什么情况呢?你懂的,这里所说是就是有关...

C# 执行固定个数任务自行控制进入线程池的线程数量,多任务同时但是并发数据限定

题目:我现在有100个任务,需要多线程去完成,但是要限定同时并发数量不能超过5个。 原理:初始启用5个线程,然后让线程中的过程执行完毕之后,自己去取下一个任务,启动下一个线程。...
  • yzy85
  • yzy85
  • 2017年04月07日 22:34
  • 2301
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C# 并发集合
举报原因:
原因补充:

(最多只允许输入30个字)