每次写博客,第一句话都是这样的:程序员很苦逼,除了会写程序,还得会写博客!

当然,题外话说多了,咱进入正题!

背景

基于任务的程序设计、命令式数据并行和任务并行都要求能够支持并发更新的数组、列表和集合。

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

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

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

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

  1. class Program
  2. {
  3. private static object o = new object();
  4. private static List<Product> _Products { get; set; }
  5. /* coder:天才卧龙
  6. * 代码中 创建三个并发线程 来操作_Products 集合
  7. * System.Collections.Generic.List 这个列表在多个线程访问下,不能保证是安全的线程,所以不能接受并发的请求,我们必须对ADD方法的执行进行串行化
  8. */
  9. static void Main(string[] args)
  10. {
  11. _Products = new List<Product>();
  12. /*创建任务 t1 t1 执行 数据集合添加操作*/
  13. Task t1 = Task.Factory.StartNew(() =>
  14. {
  15. AddProducts();
  16. });
  17. /*创建任务 t2 t2 执行 数据集合添加操作*/
  18. Task t2 = Task.Factory.StartNew(() =>
  19. {
  20. AddProducts();
  21. });
  22. /*创建任务 t3 t3 执行 数据集合添加操作*/
  23. Task t3 = Task.Factory.StartNew(() =>
  24. {
  25. AddProducts();
  26. });
  27. Task.WaitAll(t1, t2, t3);
  28. Console.WriteLine(_Products.Count);
  29. Console.ReadLine();
  30. }
  31.  
  32. /*执行集合数据添加操作*/
  33. static void AddProducts()
  34. {
  35. Parallel.For(0, 1000, (i) =>
  36. {
  37. Product product = new Product();
  38. product.Name = "name" + i;
  39. product.Category = "Category" + i;
  40. product.SellPrice = i;
  41. _Products.Add(product);
  42. });
  43.  
  44. }
  45. }
  46.  
  47. class Product
  48. {
  49. public string Name { get; set; }
  50. public string Category { get; set; }
  51. public int SellPrice { get; set; }
  52. }

代码中开启了三个并发操作,每个操作都向集合中添加1000条数据,在没有保障线程安全和串行化的运行下,实际得到的数据并没有3000条,结果如下:

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

  1. class Program
  2. {
  3. private static object o = new object();
  4. private static List<Product> _Products { get; set; }
  5. /* coder:天才卧龙
  6. * 代码中 创建三个并发线程 来操作_Products 集合
  7. * System.Collections.Generic.List 这个列表在多个线程访问下,不能保证是安全的线程,所以不能接受并发的请求,我们必须对ADD方法的执行进行串行化
  8. */
  9. static void Main(string[] args)
  10. {
  11. _Products = new List<Product>();
  12. /*创建任务 t1 t1 执行 数据集合添加操作*/
  13. Task t1 = Task.Factory.StartNew(() =>
  14. {
  15. AddProducts();
  16. });
  17. /*创建任务 t2 t2 执行 数据集合添加操作*/
  18. Task t2 = Task.Factory.StartNew(() =>
  19. {
  20. AddProducts();
  21. });
  22. /*创建任务 t3 t3 执行 数据集合添加操作*/
  23. Task t3 = Task.Factory.StartNew(() =>
  24. {
  25. AddProducts();
  26. });
  27. Task.WaitAll(t1, t2, t3);
  28. Console.WriteLine("当前数据量为:" + _Products.Count);
  29. Console.ReadLine();
  30. }
  31.  
  32. /*执行集合数据添加操作*/
  33. static void AddProducts()
  34. {
  35. Parallel.For(0, 1000, (i) =>
  36. {
  37. Product product = new Product();
  38. product.Name = "name" + i;
  39. product.Category = "Category" + i;
  40. product.SellPrice = i;
  41. lock (o)
  42. {
  43. _Products.Add(product);
  44. }
  45. });
  46.  
  47. }
  48. }
  49.  
  50. class Product
  51. {
  52. public string Name { get; set; }
  53. public string Category { get; set; }
  54. public int SellPrice { get; set; }
  55. }

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

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   提供线程安全的后进先出集合

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

ConcurrentQueue 

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

  1. class Program
  2. {
  3. private static object o = new object();
  4. /*定义 Queue*/
  5. private static Queue<Product> _Products { get; set; }
  6. private static ConcurrentQueue<Product> _ConcurrenProducts { get; set; }
  7. /* coder:天才卧龙
  8. * 代码中 创建三个并发线程 来操作_Products 和 _ConcurrenProducts 集合,每次添加 10000 条数据 查看 一般队列Queue 和 多线程安全下的队列ConcurrentQueue 执行情况
  9. */
  10. static void Main(string[] args)
  11. {
  12. Thread.Sleep(1000);
  13. _Products = new Queue<Product>();
  14. Stopwatch swTask = new Stopwatch();
  15. swTask.Start();
  16.  
  17. /*创建任务 t1 t1 执行 数据集合添加操作*/
  18. Task t1 = Task.Factory.StartNew(() =>
  19. {
  20. AddProducts();
  21. });
  22. /*创建任务 t2 t2 执行 数据集合添加操作*/
  23. Task t2 = Task.Factory.StartNew(() =>
  24. {
  25. AddProducts();
  26. });
  27. /*创建任务 t3 t3 执行 数据集合添加操作*/
  28. Task t3 = Task.Factory.StartNew(() =>
  29. {
  30. AddProducts();
  31. });
  32.  
  33. Task.WaitAll(t1, t2, t3);
  34. swTask.Stop();
  35. Console.WriteLine("List<Product> 当前数据量为:" + _Products.Count);
  36. Console.WriteLine("List<Product> 执行时间为:" + swTask.ElapsedMilliseconds);
  37.  
  38. Thread.Sleep(1000);
  39. _ConcurrenProducts = new ConcurrentQueue<Product>();
  40. Stopwatch swTask1 = new Stopwatch();
  41. swTask1.Start();
  42.  
  43. /*创建任务 tk1 tk1 执行 数据集合添加操作*/
  44. Task tk1 = Task.Factory.StartNew(() =>
  45. {
  46. AddConcurrenProducts();
  47. });
  48. /*创建任务 tk2 tk2 执行 数据集合添加操作*/
  49. Task tk2 = Task.Factory.StartNew(() =>
  50. {
  51. AddConcurrenProducts();
  52. });
  53. /*创建任务 tk3 tk3 执行 数据集合添加操作*/
  54. Task tk3 = Task.Factory.StartNew(() =>
  55. {
  56. AddConcurrenProducts();
  57. });
  58.  
  59. Task.WaitAll(tk1, tk2, tk3);
  60. swTask1.Stop();
  61. Console.WriteLine("ConcurrentQueue<Product> 当前数据量为:" + _ConcurrenProducts.Count);
  62. Console.WriteLine("ConcurrentQueue<Product> 执行时间为:" + swTask1.ElapsedMilliseconds);
  63. Console.ReadLine();
  64. }
  65.  
  66. /*执行集合数据添加操作*/
  67. static void AddProducts()
  68. {
  69. Parallel.For(0, 30000, (i) =>
  70. {
  71. Product product = new Product();
  72. product.Name = "name" + i;
  73. product.Category = "Category" + i;
  74. product.SellPrice = i;
  75. lock (o)
  76. {
  77. _Products.Enqueue(product);
  78. }
  79. });
  80.  
  81. }
  82. /*执行集合数据添加操作*/
  83. static void AddConcurrenProducts()
  84. {
  85. Parallel.For(0, 30000, (i) =>
  86. {
  87. Product product = new Product();
  88. product.Name = "name" + i;
  89. product.Category = "Category" + i;
  90. product.SellPrice = i;
  91. _ConcurrenProducts.Enqueue(product);
  92. });
  93.  
  94. }
  95. }
  96.  
  97. class Product
  98. {
  99. public string Name { get; set; }
  100. public string Category { get; set; }
  101. public int SellPrice { get; set; }
  102. }

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

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

  1. class Program
  2. {
  3. private static object o = new object();
  4. private static ConcurrentQueue<Product> _ConcurrenProducts { get; set; }
  5. /* coder:天才卧龙
  6. * ConcurrentQueue 下的 TryPeek 和 TryDequeue
  7. */
  8. static void Main(string[] args)
  9. {
  10. _ConcurrenProducts = new ConcurrentQueue<Product>();
  11. /*执行添加操作*/
  12. Console.WriteLine("执行添加操作");
  13. Parallel.Invoke(AddConcurrenProducts, AddConcurrenProducts);
  14. Console.WriteLine("ConcurrentQueue<Product> 当前数据量为:" + _ConcurrenProducts.Count);
  15. /*执行TryPeek操作 尝试返回不移除*/
  16. Console.WriteLine("执行TryPeek操作 尝试返回不移除");
  17. Parallel.Invoke(PeekConcurrenProducts, PeekConcurrenProducts);
  18. Console.WriteLine("ConcurrentQueue<Product> 当前数据量为:" + _ConcurrenProducts.Count);
  19.  
  20. /*执行TryDequeue操作 尝试返回并移除*/
  21. Console.WriteLine("执行TryDequeue操作 尝试返回并移除");
  22. Parallel.Invoke(DequeueConcurrenProducts, DequeueConcurrenProducts);
  23. Console.WriteLine("ConcurrentQueue<Product> 当前数据量为:" + _ConcurrenProducts.Count);
  24.  
  25. Console.ReadLine();
  26. }
  27.  
  28. /*执行集合数据添加操作*/
  29. static void AddConcurrenProducts()
  30. {
  31. Parallel.For(0, 100, (i) =>
  32. {
  33. Product product = new Product();
  34. product.Name = "name" + i;
  35. product.Category = "Category" + i;
  36. product.SellPrice = i;
  37. _ConcurrenProducts.Enqueue(product);
  38. });
  39. }
  40. /*尝试返回 但不移除*/
  41. static void PeekConcurrenProducts()
  42. {
  43. Parallel.For(0, 2, (i) =>
  44. {
  45. Product product = null;
  46. bool excute = _ConcurrenProducts.TryPeek(out product);
  47. Console.WriteLine(product.Name);
  48. });
  49. }
  50. /*尝试返回 并 移除*/
  51. static void DequeueConcurrenProducts()
  52. {
  53. Parallel.For(0, 2, (i) =>
  54. {
  55. Product product = null;
  56. bool excute = _ConcurrenProducts.TryDequeue(out product);
  57. Console.WriteLine(product.Name);
  58. });
  59. }
  60. }
  61.  
  62. class Product
  63. {
  64. public string Name { get; set; }
  65. public string Category { get; set; }
  66. public int SellPrice { get; set; }
  67. }

需要注意 TryDequeue  和  TryPeek 的无序性,在多线程下

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

  1. private static object o = new object();
  2. /*定义 Stack*/
  3. private static Stack<Product> _Products { get; set; }
  4. private static ConcurrentStack<Product> _ConcurrenProducts { get; set; }
  5. /* coder:天才卧龙
  6. * 代码中 创建三个并发线程 来操作_Products 和 _ConcurrenProducts 集合,每次添加 30000 条数据 查看 一般Stack 和 多线程安全下的 ConcurrentStack 执行情况
  7. */
  8. static void Main(string[] args)
  9. {
  10. Thread.Sleep(1000);
  11. _Products = new Stack<Product>();
  12. Stopwatch swTask = new Stopwatch();
  13. swTask.Start();
  14.  
  15. /*创建任务 t1 t1 执行 数据集合添加操作*/
  16. Task t1 = Task.Factory.StartNew(() =>
  17. {
  18. AddProducts();
  19. });
  20. /*创建任务 t2 t2 执行 数据集合添加操作*/
  21. Task t2 = Task.Factory.StartNew(() =>
  22. {
  23. AddProducts();
  24. });
  25. /*创建任务 t3 t3 执行 数据集合添加操作*/
  26. Task t3 = Task.Factory.StartNew(() =>
  27. {
  28. AddProducts();
  29. });
  30.  
  31. Task.WaitAll(t1, t2, t3);
  32. swTask.Stop();
  33. Console.WriteLine("List<Product> 当前数据量为:" + _Products.Count);
  34. Console.WriteLine("List<Product> 执行时间为:" + swTask.ElapsedMilliseconds);
  35.  
  36. Thread.Sleep(1000);
  37. _ConcurrenProducts = new ConcurrentStack<Product>();
  38. Stopwatch swTask1 = new Stopwatch();
  39. swTask1.Start();
  40.  
  41. /*创建任务 tk1 tk1 执行 数据集合添加操作*/
  42. Task tk1 = Task.Factory.StartNew(() =>
  43. {
  44. AddConcurrenProducts();
  45. });
  46. /*创建任务 tk2 tk2 执行 数据集合添加操作*/
  47. Task tk2 = Task.Factory.StartNew(() =>
  48. {
  49. AddConcurrenProducts();
  50. });
  51. /*创建任务 tk3 tk3 执行 数据集合添加操作*/
  52. Task tk3 = Task.Factory.StartNew(() =>
  53. {
  54. AddConcurrenProducts();
  55. });
  56.  
  57. Task.WaitAll(tk1, tk2, tk3);
  58. swTask1.Stop();
  59. Console.WriteLine("ConcurrentStack<Product> 当前数据量为:" + _ConcurrenProducts.Count);
  60. Console.WriteLine("ConcurrentStack<Product> 执行时间为:" + swTask1.ElapsedMilliseconds);
  61. Console.ReadLine();
  62. }
  63.  
  64. /*执行集合数据添加操作*/
  65. static void AddProducts()
  66. {
  67. Parallel.For(0, 30000, (i) =>
  68. {
  69. Product product = new Product();
  70. product.Name = "name" + i;
  71. product.Category = "Category" + i;
  72. product.SellPrice = i;
  73. lock (o)
  74. {
  75. _Products.Push(product);
  76. }
  77. });
  78.  
  79. }
  80. /*执行集合数据添加操作*/
  81. static void AddConcurrenProducts()
  82. {
  83. Parallel.For(0, 30000, (i) =>
  84. {
  85. Product product = new Product();
  86. product.Name = "name" + i;
  87. product.Category = "Category" + i;
  88. product.SellPrice = i;
  89. _ConcurrenProducts.Push(product);
  90. });
  91.  
  92. }
  93. }
  94.  
  95. class Product
  96. {
  97. public string Name { get; set; }
  98. public string Category { get; set; }
  99. public int SellPrice { get; set; }
  100. }

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

  1. class Program
  2. {
  3. private static object o = new object();
  4. private static ConcurrentStack<Product> _ConcurrenProducts { get; set; }
  5. /* coder:天才卧龙
  6. * ConcurrentQueue 下的 TryPeek 和 TryPop
  7. */
  8. static void Main(string[] args)
  9. {
  10. _ConcurrenProducts = new ConcurrentStack<Product>();
  11. /*执行添加操作*/
  12. Console.WriteLine("执行添加操作");
  13. Parallel.Invoke(AddConcurrenProducts, AddConcurrenProducts);
  14. Console.WriteLine("ConcurrentStack<Product> 当前数据量为:" + _ConcurrenProducts.Count);
  15. /*执行TryPeek操作 尝试返回不移除*/
  16. Console.WriteLine("执行TryPeek操作 尝试返回不移除");
  17. Parallel.Invoke(PeekConcurrenProducts, PeekConcurrenProducts);
  18. Console.WriteLine("ConcurrentStack<Product> 当前数据量为:" + _ConcurrenProducts.Count);
  19.  
  20. /*执行TryDequeue操作 尝试返回并移除*/
  21. Console.WriteLine("执行TryPop操作 尝试返回并移除");
  22. Parallel.Invoke(PopConcurrenProducts, PopConcurrenProducts);
  23. Console.WriteLine("ConcurrentStack<Product> 当前数据量为:" + _ConcurrenProducts.Count);
  24.  
  25. Console.ReadLine();
  26. }
  27.  
  28. /*执行集合数据添加操作*/
  29. static void AddConcurrenProducts()
  30. {
  31. Parallel.For(0, 100, (i) =>
  32. {
  33. Product product = new Product();
  34. product.Name = "name" + i;
  35. product.Category = "Category" + i;
  36. product.SellPrice = i;
  37. _ConcurrenProducts.Push(product);
  38. });
  39. }
  40. /*尝试返回 但不移除*/
  41. static void PeekConcurrenProducts()
  42. {
  43. Parallel.For(0, 2, (i) =>
  44. {
  45. Product product = null;
  46. bool excute = _ConcurrenProducts.TryPeek(out product);
  47. Console.WriteLine(product.Name);
  48. });
  49. }
  50. /*尝试返回 并 移除*/
  51. static void PopConcurrenProducts()
  52. {
  53. Parallel.For(0, 2, (i) =>
  54. {
  55. Product product = null;
  56. bool excute = _ConcurrenProducts.TryPop(out product);
  57. Console.WriteLine(product.Name);
  58. });
  59. }
  60. }
  61.  
  62. class Product
  63. {
  64. public string Name { get; set; }
  65. public string Category { get; set; }
  66. public int SellPrice { get; set; }
  67. }

对于并发下的其他集合,我这边就不做代码案列了,大家可以通过下面的链接查看,如有问题,欢迎指正

C#并发处理-锁OR线程安全?的更多相关文章

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

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

  2. C# 集合-并发处理-锁OR线程 (转载)

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

  3. Lock锁_线程_线程域

    using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using Sy ...

  4. 无锁,线程安全,延迟加载的单例实现(C#)

    单例(singleton)是非常常见,也非常有用的设计模式,当然了, 面试中也是经常会被问到的:)在几乎所有的项目中都能看到它的身影.简而言之,单例保证了一个自定义类型在整个程序的生命周期只被创建一次 ...

  5. C#多线程实践——锁和线程安全

    锁实现互斥的访问,用于确保在同一时刻只有一个线程可以进入特殊的代码片段,考虑下面的类: class ThreadUnsafe { static int val1, val2; static void ...

  6. 不使用synchronized和lock 锁实现线程安全单例

    单例实现方式一,锁机制 public class Singleton { private static Singleton singleton=null; public Singleton() { } ...

  7. “全栈2019”Java多线程第三十一章:中断正在等待显式锁的线程

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  8. python网络编程--线程(锁,GIL锁,守护线程)

    1.线程 1.进程与线程 进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率.很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观 ...

  9. python多线程编程(3): 使用互斥锁同步线程

    问题的提出 上一节的例子中,每个线程互相独立,相互之间没有任何关系.现在假设这样一个例子:有一个全局的计数num,每个线程获取这个全局的计数,根据num进行一些处理,然后将num加1.很容易写出这样的 ...

随机推荐

  1. PHP 错误与异常 笔记与总结(3)PHP 配置文件(php.ini)中与错误相关的选项 与 设置错误级别

    [PHP 配置文件中与错误相关的选项 ] 选项 描述 error_reporting 设置错误报告的级别 display_errors 是否显示错误 log_errors 设置是否将错误信息记录到日志 ...

  2. GDC2016【全境封锁(Tom Clancy's The Division)】对为何对应Eye Tracked System,以及各种优点的演讲报告

    GDC2016[全境封锁(Tom Clancy's The Division)]对为何对应Eye Tracked System,以及各种优点的演讲报告 原文 4Gamer編集部:松本隆一 http:/ ...

  3. PHP Console工具使用分享

    PHP Console工具使用分享 http://www.open-open.com/lib/view/open1416193590414.html 您的评价:       不错  收藏该经验     ...

  4. Bootstrap页面布局20 - BS缩略图

    <div class='container-fluid'> <h2 class='page-header'>Bootstrap 缩略图</h2> <ul cl ...

  5. Lazarus -Pascal常量

    2.常量 2.1.普通常量 仅仅下面类型可以被定义为常量Ordinal类型Set类型指针类型 (but the only allowed value is Nil). real类型 Char, Str ...

  6. 浮动以后父DIV包不住子DIV解决方案

    转载自http://blog.sina.com.cn/s/blog_6c363acf0100v4cz.html 当DIV1里面嵌套有一个DIV2,当DIV2设置了浮动,那么DIV1是无法被撑开的,也就 ...

  7. JBoss的安装与配置(对应eclipse配置)【转】

    安装JBoss纯粹是目的就是学习EJB3...至少现在是这样的 EJB需要运行在EJB容器中.每个J2EE应用服务器都含有EJB容器和Web容器.这样,既支持运行EJB,也可以运行Web应用 目前EJ ...

  8. 【翻译】How To Tango With Django 1.5.4 第一章

    1.概览 这本书的目的就是为了给你提供Django实战开发的指导,这本书主要是为学生设计的,它提供了开发并运行第一个web应用程序的详细的指导步骤,并且指导你怎么将它发布到web服务器上. 本书就是为 ...

  9. node.js的exprots工厂模式

    工厂类: /** * Created by zzq on 2015/5/15. */ module.exports = function(){ this.getProduct = function() ...

  10. [LeetCode] Regular Expression Matching(递归)

    Implement regular expression matching with support for '.' and '*'. '.' Matches any single character ...