• 4.0中的并行计算和多线程详解


    并行计算部分

            沿用微软的写法,System.Threading.Tasks.::.Parallel类,提供对并行循环和区域的支持。 我们会用到的方法有For,ForEach,Invoke。

    一、简单使用

            首先我们初始化一个List用于循环,这里我们循环10次。(后面的代码都会按这个标准进行循环)

    Code
    1.             Program.Data = new List<int>();
    2.             for (int i = 0; i < 10; i++)
    3.             {
    4.                 Data.Add(i);
    5.             }

            下面我们定义4个方法,分别为for,foreach,并行For,并行ForEach。并测试他们的运行时长。

    Code
    1.         /// <summary>
    2.         /// 是否显示执行过程
    3.         /// </summary>
    4.         public bool ShowProcessExecution = false;
    5.         /// <summary>
    6.         /// 这是普通循环for
    7.         /// </summary>
    8.         private void Demo1()
    9.         {
    10.             List<int> data = Program.Data;
    11.             DateTime dt1 = DateTime.Now;
    12.             for (int i = 0; i < data.Count; i++)
    13.             {
    14.                 Thread.Sleep(500);
    15.                 if (ShowProcessExecution)
    16.                     Console.WriteLine(data[i]);
    17.             }
    18.             DateTime dt2 = DateTime.Now;
    19.             Console.WriteLine("普通循环For运行时长:{0}毫秒。", (dt2 - dt1).TotalMilliseconds);
    20.         }
    21.         /// <summary>
    22.         /// 这是普通循环foreach
    23.         /// </summary>
    24.         private void Demo2()
    25.         {
    26.             List<int> data = Program.Data;
    27.             DateTime dt1 = DateTime.Now;
    28.             foreach (var in data)
    29.             {
    30.                 Thread.Sleep(500);
    31.                 if (ShowProcessExecution)
    32.                     Console.WriteLine(i);
    33.             }
    34.             DateTime dt2 = DateTime.Now;
    35.             Console.WriteLine("普通循环For运行时长:{0}毫秒。", (dt2 - dt1).TotalMilliseconds);
    36.         }
    37.         /// <summary>
    38.         /// 这是并行计算For
    39.         /// </summary>
    40.         private void Demo3()
    41.         {
    42.             List<int> data = Program.Data;
    43.             DateTime dt1 = DateTime.Now;
    44.             Parallel.For(0, data.Count, (i) =>
    45.             {
    46.                 Thread.Sleep(500);
    47.                 if (ShowProcessExecution)
    48.                     Console.WriteLine(data[i]);
    49.             });
    50.             DateTime dt2 = DateTime.Now;
    51.             Console.WriteLine("并行运算For运行时长:{0}毫秒。", (dt2 - dt1).TotalMilliseconds);
    52.         }
    53.         /// <summary>
    54.         /// 这是并行计算ForEach
    55.         /// </summary>
    56.         private void Demo4()
    57.         {
    58.             List<int> data = Program.Data;
    59.             DateTime dt1 = DateTime.Now;
    60.             Parallel.ForEach(data, (i) =>
    61.             {
    62.                 Thread.Sleep(500);
    63.                 if (ShowProcessExecution)
    64.                     Console.WriteLine(i);
    65.             });
    66.             DateTime dt2 = DateTime.Now;
    67.             Console.WriteLine("并行运算ForEach运行时长:{0}毫秒。", (dt2 - dt1).TotalMilliseconds);
    68.         }

    下面是运行结果:

    image

    这里我们可以看出并行循环在执行效率上的优势了。

    结论1:在对一个数组内的每一个项做单独处理时,完全可以选择并行循环的方式来提升执行效率。

    原理1:并行计算的线程开启是缓步开启的,线程数量1,2,4,8缓步提升。(不详,PLinq最多64个线程,可能这也是64)

      

      

    二、 并行循环的中断和跳出

            当在进行循环时,偶尔会需要中断循环或跳出循环。下面是两种跳出循环的方法Stop和Break,LoopState是循环状态的参数。

    Code
    1.         /// <summary>
    2.         /// 中断Stop
    3.         /// </summary>
    4.         private void Demo5()
    5.         {
    6.             List<int> data = Program.Data;
    7.             Parallel.For(0, data.Count, (i, LoopState) =>
    8.             {
    9.                 if (data[i] > 5)
    10.                     LoopState.Stop();
    11.                 Thread.Sleep(500);
    12.                 Console.WriteLine(data[i]);
    13.             });
    14.             Console.WriteLine("Stop执行结束。");
    15.         }
    16.         /// <summary>
    17.         /// 中断Break
    18.         /// </summary>
    19.         private void Demo6()
    20.         {
    21.             List<int> data = Program.Data;
    22.             Parallel.ForEach(data, (i, LoopState) =>
    23.             {
    24.                 if (i > 5)
    25.                     LoopState.Break();
    26.                 Thread.Sleep(500);
    27.                 Console.WriteLine(i);
    28.             });
    29.             Console.WriteLine("Break执行结束。");
    30.         }

            执行结果如下:

    image

    结论2:使用Stop会立即停止循环,使用Break会执行完毕所有符合条件的项。

      

      

    三、并行循环中为数组/集合添加项

            上面的应用场景其实并不是非常多见,毕竟只是为了遍历一个数组内的资源,我们更多的时候是为了遍历资源,找到我们所需要的。那么请继续看。

    下面是我们一般会想到的写法:

    Code
    1.         private void Demo7()
    2.         {
    3.             List<int> data = new List<int>();
    4.             Parallel.For(0, Program.Data.Count, (i) =>
    5.             {
    6.                 if (Program.Data[i] % 2 == 0)
    7.                     data.Add(Program.Data[i]);
    8.             });
    9.             Console.WriteLine("执行完成For.");
    10.         }
    11.         private void Demo8()
    12.         {
    13.             List<int> data = new List<int>();
    14.             Parallel.ForEach(Program.Data, (i) =>
    15.             {
    16.                 if (Program.Data[i] % 2 == 0)
    17.                     data.Add(Program.Data[i]);
    18.             });
    19.             Console.WriteLine("执行完成ForEach.");
    20.         }

    看起来应该是没有问题的,但是我们多次运行后会发现,偶尔会出现错误如下:

    image

    这是因为List是非线程安全的类,我们需要使用System.Collections.Concurrent命名空间下的类型来用于并行循环体内。

    说明
    BlockingCollection<T> 为实现 IProducerConsumerCollection<T> 的线程安全集合提供阻止和限制功能。
    ConcurrentBag<T> 表示对象的线程安全的无序集合。
    ConcurrentDictionary<TKey, TValue> 表示可由多个线程同时访问的键值对的线程安全集合。
    ConcurrentQueue<T> 表示线程安全的先进先出 (FIFO) 集合。
    ConcurrentStack<T> 表示线程安全的后进先出 (LIFO) 集合。
    OrderablePartitioner<TSource> 表示将一个可排序数据源拆分成多个分区的特定方式。
    Partitioner 提供针对数组、列表和可枚举项的常见分区策略。
    Partitioner<TSource> 表示将一个数据源拆分成多个分区的特定方式。

    公共类

    那么我们上面的代码可以修改为,加了了ConcurrentQueue和ConcurrentStack的最基本的操作。

    Code
    1.         /// <summary>
    2.         /// 并行循环操作集合类,集合内只取5个对象
    3.         /// </summary>
    4.         private void Demo7()
    5.         {
    6.             ConcurrentQueue<int> data = new ConcurrentQueue<int>();
    7.             Parallel.For(0, Program.Data.Count, (i) =>
    8.             {
    9.                 if (Program.Data[i] % 2 == 0)
    10.                     data.Enqueue(Program.Data[i]);//将对象加入到队列末尾
    11.             });
    12.             int R;
    13.             while (data.TryDequeue(out R))//返回队列中开始处的对象
    14.             {
    15.                 Console.WriteLine(R);
    16.             }
    17.             Console.WriteLine("执行完成For.");
    18.         }
    19.         /// <summary>
    20.         /// 并行循环操作集合类
    21.         /// </summary>
    22.         private void Demo8()
    23.         {
    24.             ConcurrentStack<int> data = new ConcurrentStack<int>();
    25.             Parallel.ForEach(Program.Data, (i) =>
    26.             {
    27.                 if (Program.Data[i] % 2 == 0)
    28.                     data.Push(Program.Data[i]);//将对象压入栈中
    29.             });
    30.             int R;
    31.             while (data.TryPop(out R))//弹出栈顶对象
    32.             {
    33.                 Console.WriteLine(R);
    34.             }
    35.             Console.WriteLine("执行完成ForEach.");
    36.         }

    ok,这里返回一个序列的问题也解决了。

    结论3:在并行循环内重复操作的对象,必须要是thread-safe(线程安全)的。集合类的线程安全对象全部在System.Collections.Concurrent命名空间下。

      

      

    四、返回集合运算结果/含有局部变量的并行循环

            使用循环的时候经常也会用到迭代,那么在并行循环中叫做 含有局部变量的循环 。下面的代码中详细的解释,这里就不啰嗦了。

    Code
    1.         /// <summary>
    2.         /// 具有线程局部变量的For循环
    3.         /// </summary>
    4.         private void Demo9()
    5.         {
    6.             List<int> data = Program.Data;
    7.             long total = 0;
    8.             //这里定义返回值为long类型方便下面各个参数的解释
    9.             Parallel.For<long>(0,           // For循环的起点
    10.                 data.Count,                 // For循环的终点
    11.                 () => 0,                    // 初始化局部变量的方法(long),既为下面的subtotal的初值
    12.                 (i, LoopState, subtotal) => // 为每个迭代调用一次的委托,i是当前索引,LoopState是循环状态,subtotal为局部变量名
    13.                 {
    14.                     subtotal += data[i];    // 修改局部变量
    15.                     return subtotal;        // 传递参数给下一个迭代
    16.                 },
    17.                 (finalResult) => Interlocked.Add(ref total, finalResult) //对每个线程结果执行的最后操作,这里是将所有的结果相加
    18.                 );
    19.             Console.WriteLine(total);
    20.         }
    21.         /// <summary>
    22.         /// 具有线程局部变量的ForEach循环
    23.         /// </summary>
    24.         private void Demo10()
    25.         {
    26.             List<int> data = Program.Data;
    27.             long total = 0;
    28.             Parallel.ForEach<intlong>(data, // 要循环的集合对象
    29.                 () => 0,                      // 初始化局部变量的方法(long),既为下面的subtotal的初值
    30.                 (i, LoopState, subtotal) =>   // 为每个迭代调用一次的委托,i是当前元素,LoopState是循环状态,subtotal为局部变量名
    31.                 {
    32.                     subtotal += i;            // 修改局部变量
    33.                     return subtotal;          // 传递参数给下一个迭代
    34.                 },
    35.                 (finalResult) => Interlocked.Add(ref total, finalResult) //对每个线程结果执行的最后操作,这里是将所有的结果相加
    36.                 );
    37.             Console.WriteLine(total);
    38.         }

    结论4:并行循环中的迭代,确实很伤人。代码太难理解了。

    五、PLinq(Linq的并行计算)

               上面介绍完了For和ForEach的并行计算盛宴,微软也没忘记在Linq中加入并行计算。下面介绍Linq中的并行计算。

    4.0中在System.Linq命名空间下加入了下面几个新的类:

    说明
    ParallelEnumerable 提供一组用于查询实现 ParallelQuery{TSource} 的对象的方法。这是 Enumerable 的并行等效项。
    ParallelQuery 表示并行序列。
    ParallelQuery<TSource> 表示并行序列。

    原理2:PLinq最多会开启64个线程

    原理3:PLinq会自己判断是否可以进行并行计算,如果不行则会以顺序模式运行。

    原理4:PLinq会在昂贵的并行算法或成本较低的顺序算法之间进行选择,默认情况下它选择顺序算法。

      

    在ParallelEnumerable中提供的并行化的方法

    ParallelEnumerable 运算符 说明
    AsParallel() PLINQ 的入口点。指定如果可能,应并行化查询的其余部分。
    AsSequential() 指定查询的其余部分应像非并行 LINQ 查询一样按顺序运行。
    AsOrdered() 指定 PLINQ 应保留查询的其余部分的源序列排序,直到例如通过使用 orderby 子句更改排序为止。
    AsUnordered() 指定查询的其余部分的 PLINQ 不需要保留源序列的排序。
    WithCancellation() 指定 PLINQ 应定期监视请求取消时提供的取消标记和取消执行的状态。
    WithDegreeOfParallelism() 指定 PLINQ 应当用来并行化查询的处理器的最大数目。
    WithMergeOptions() 提供有关 PLINQ 应当如何(如果可能)将并行结果合并回到使用线程上的一个序列的提示。
    WithExecutionMode() 指定 PLINQ 应当如何并行化查询(即使默认行为是按顺序运行查询)。
    ForAll() 多线程枚举方法,与循环访问查询结果不同,它允许在不首先合并回到使用者线程的情况下并行处理结果。
    Aggregate() 重载 对于 PLINQ 唯一的重载,它启用对线程本地分区的中间聚合以及一个用于合并所有分区结果的最终聚合函数。

    下面是PLinq的简单代码

    Code
    1.         /// <summary>
    2.         /// PLinq简介
    3.         /// </summary>
    4.         private void Demo11()
    5.         {
    6.             var source = Enumerable.Range(1, 10000);
    7.             //查询结果按source中的顺序排序
    8.             var evenNums = from num in source.AsParallel().AsOrdered()
    9.                        where num % 2 == 0
    10.                        select num;
    11.             //ForAll的使用
    12.             ConcurrentBag<int> concurrentBag = new ConcurrentBag<int>();
    13.             var query = from num in source.AsParallel()
    14.                         where num % 10 == 0
    15.                         select num;
    16.             query.ForAll((e) => concurrentBag.Add(e * e));
    17.         }

    上面代码中使用了ForAll,ForAll和foreach的区别如下:

    image

    PLinq的东西很繁杂,但是都只是几个简单的方法,熟悉下方法就好了。

               本打算并行循环和多线程一起写的,但是没想到一个并行计算就写了这么多,多线程只能留待下次了。

    OK,谢谢观赏!

    开发技术

     

    posted @ 2011-11-09 14:42 ☆磊☆ 阅读(369) | 评论 (0) 编辑 |

     

    posted @ 2011-11-02 09:10 ☆磊☆ 阅读(1304) | 评论 (8) 编辑 |

     

    posted @ 2011-06-30 08:37 ☆磊☆ 阅读(3615) | 评论 (23) 编辑 |

    摘要: 在项目中经常会用到一些样式什么的,如果使用了jQuery UI那么很多的图标,样式什么的,就可以尽量使用jQuery UI里面已经定义好了的,在此就对jQuery UI中的css做写了下注释,提供自己...阅读全文

    posted @ 2011-03-14 13:26 ☆磊☆ 阅读(3017) | 评论 (1) 编辑 |

     

    posted @ 2010-09-27 13:31 ☆磊☆ 阅读(1548) | 评论 (2) 编辑 |

     

    posted @ 2010-09-18 12:38 ☆磊☆ 阅读(1635) | 评论 (2) 编辑 |

     

    posted @ 2010-09-16 16:21 ☆磊☆ 阅读(2249) | 评论 (8) 编辑 |

     

    posted @ 2010-08-16 15:32 ☆磊☆ 阅读(2058) | 评论 (6) 编辑 |

     

    posted @ 2010-03-10 15:34 ☆磊☆ 阅读(4238) | 评论 (15) 编辑 |

    工具

    软件工具

    摘要: 在Visual Studio中的智能感知,相信大家都用过。summary,param,returns这几个相信很多人都用过的吧。那么field,value等等这些呢。阅读全文

    posted @ 2011-12-15 15:55 ☆磊☆ 阅读(1214) | 评论 (5) 编辑 |

    摘要: 之前写过一篇Windows下安装和使用UglifyJS对JavaScript进行压缩或美化的文章,该文中使用的是Cygwin安装UglifyJS,主要问题就是安装Cygwin的时间会很长,而且安装还不一定成功。 现在NodeJS也有windows版本了,所以写了本文,我们用原生的NodeJS来运行UglifyJS。阅读全文

    posted @ 2011-12-14 15:34 ☆磊☆ 阅读(543) | 评论 (1) 编辑 |

     

    posted @ 2011-02-26 20:03 ☆磊☆ 阅读(1472) | 评论 (9) 编辑 |

    摘要: 前一篇文章介紹了如何在 Cygwin 環境下安裝 node.js,我的原始目的只是為了要能正常執行UglifyJS 而已,原本想說在 node.js 寫的程式都是 JavaScript 應該不會有什麼問題吧,結果是目前版本的 UglifyJS 還有點小問題導致無法正常執行,解決方式也很簡單,以下是 UglifyJS 的安裝與使用說明。阅读全文

    posted @ 2011-02-21 16:28 ☆磊☆ 阅读(344) | 评论 (0) 编辑 |

    摘要: 下面分别介绍在Mac, Ubuntu,Centos以及Windows下安装Node.js.阅读全文

    posted @ 2011-02-21 16:17 ☆磊☆ 阅读(1063) | 评论 (0) 编辑 |

     

    posted @ 2010-09-16 08:46 ☆磊☆ 阅读(341) | 评论 (0) 编辑 |

    这里记录的是一些用于开发的软件的使用。

    摘要: 上一篇中已经介绍了如何在windows下安装和配置Git,并且详细说明了如何和GitHub连接。这里就详细的说明下如何在Visual Studio中使用GitHub。这里也是一个简单的Git教程。注:非常非常简单的教程阅读全文

    posted @ 2011-08-11 11:27 ☆磊☆ 阅读(1403) | 评论 (3) 编辑 |

    摘要: Git在源码管理领域目前占很大的比重了,而且开源的项目很多都转到GitHub上面了。例如:jQuery, reddit, Sparkle, curl, Ruby on Rails, node.js, ClickToFlash, Erlang/OTP, CakePHP, Redis. 本文详细的说明了如何在Windows下安装配置Git工具连接GitHub。并可以在Visual Studio中使用Git。阅读全文

    posted @ 2011-08-10 08:58 ☆磊☆ 阅读(1738) | 评论 (7) 编辑 |

     

    posted @ 2009-12-24 16:27 ☆磊☆ 阅读(4958) | 评论 (11) 编辑 |

  • 相关阅读:
    SpringMVC(一)
    Mybatis二(高级部分)
    Mybatis一(基础)
    泛型
    itcast-Hibernate orm元数据和 关系操作
    自动装箱自动拆箱,基本数据类型
    struts2 中的数据访问servletAPI
    Hibernate 查询
    itcast-ssh-crm实践
    final修饰符(2)
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2289469.html
Copyright © 2020-2023  润新知