zoukankan      html  css  js  c++  java
  • Net 4.0并行库实用性演练

    引言

    随着CPU多核的普及,编程时充分利用这个特性越显重要。上篇首先用传统的嵌套循环进行数组填充,然后用.NET 4.0中的System.Threading.Tasks提供的Parallel Class来并行地进行填充,最后对比他们的性能。本文将深入分析Parallel Class并借机回答上篇9楼提出的问题,而System.Threading.Tasks分析,这个将推迟到.NET(C#) Internals: 以一个数组填充的例子初步了解.NET 4.0中的并行(三)中介绍。内容如下:

    • 1、Parallel Class
      • 1.1、For方法
      • 1.2、ForEach方法
      • 1.3、Invoke方法
    • 2、并发控制疑问?
      • 2.1、使用Lock锁
      • 2.2、使用PLINQ——用AsParallel
      • 2.3、使用PLINQ——用ParallelEnumerable
      • 2.4、使用Interlocked操作
      • 2.5、使用Parallel.For的有Thread-Local变量重载函数
    • 性能比较

    1、Parallel Class

    Parallel——这个类提供对通常操作(诸如for、foreach、执行语句块)基于库的数据并行替换。它只是System.Threading.Tasks命名空间的一个类,该命名空间中还包括很多其他的类。下面举个例子来说明如何使用Parallel.For(来自MSDN):

    01 using System.Threading.Tasks;   
    02 class Test
    03 {
    04     static int N = 1000;
    05   
    06     static void TestMethod()
    07     {
    08         // Using a named method.
    09         Parallel.For(0, N, Method2);
    10   
    11         // Using an anonymous method.
    12         Parallel.For(0, N, delegate(int i)
    13         {
    14             // Do Work.
    15         });
    16   
    17         // Using a lambda expression.
    18         Parallel.For(0, N, i =>
    19         {
    20             // Do Work.
    21         });
    22     }
    23   
    24     static void Method2(int i)
    25     {
    26         // Do work.
    27     }
    28 }

    上面这个例子简单易懂,上篇我们就是用的Parallel.For,这里就不解释了。其实Parallel类的方法主要分为下面三类:

    • For方法
    • ForEach方法
    • Invoke方法

    1.1、For方法

    在里面执行的for循环可能并行地运行,它有12个重载。这12个重载中Int32参数和Int64参数的方法各为6个,下面以Int32为例列出:

    下面代码演示了For(Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action<Int32> body)方法(来自MSDN):

    1.2、ForEach方法

    在迭代中执行的foreach操作可能并行地执行,它有20个重载。这个方法太多,但用法大概跟For方法差不多,请自行参考MSDN

    1.3、Invoke方法

    提供的每个动作可能并行地执行,它有2个重载。

    例如下面代码执行了三个操作(来自MSDN):

    2、并发控制疑问?

    有人提出以下疑问:“如果For里面的东西,对于顺序敏感的话,会不会有问题。并行处理的话,说到底应该是多线程。如果需要Lock住什么东西的话,应该怎么做呢?例如这个例子不是对数组填充,是对文件操作呢?对某个资源操作呢?”

    关于对顺序敏感的话,也就是说该如何加锁来控制?下面我举个例子来说明:对1~1000求和。如果我们想上篇那样简单地用Parallel.For,将会产生错误的结果,代码如下:

    01 using System;
    02 using System.Collections.Generic;
    03 using System.Linq;
    04 using System.Text;
    05 using System.Threading;
    06 using System.Threading.Tasks;
    07   
    08 namespace ConsoleApplication2
    09 {
    10     class Program
    11     {        
    12         static void Main(string[] args)
    13         {
    14             int loops=0;
    15             while (loops <= 100)
    16             {
    17                 long sum = 0;                
    18                 Parallel.For(1, 1001, delegate(long i)
    19                 {
    20                     sum += i;
    21                 });
    22                 System.Console.WriteLine(sum);
    23                 loops++;
    24             }
    25         }
    26     }
    27 }

    在上述代码中,为了校验正确性我进行了重复做了100次,得出如下结果:

    image图1、100次的前面部分结果

    我们知道500500才是正确的答案,这说明Parallel.For不能保证对sum正确的并发执行,对此我们应该加上适当的控制,并借机来回答上面提出的如何加锁的问题。下面有几种方案可以解决这个问题:

    2.1、使用Lock锁

    这个我就不多解释了,直接上代码:

    01 using System;
    02 using System.Collections.Generic;
    03 using System.Linq;
    04 using System.Text;
    05 using System.Threading;
    06 using System.Threading.Tasks;
    07   
    08 namespace ConsoleApplication2
    09 {
    10     class Program
    11     {
    12         static void Main(string[] args)
    13         {
    14             int loops = 0;
    15             object moniter = new object();
    16             while (loops <= 100)
    17             {
    18                 long sum = 0;
    19                 Parallel.For(1, 1001, delegate(long i)
    20                 {
    21                     lock (moniter) { sum += i; }
    22                 });
    23                 System.Console.WriteLine(sum);
    24                 loops++;
    25             }
    26         }
    27     }
    28 }

    我们加上lock锁之后就会得出正确的结果。

    2.2、使用PLINQ——用AsParallel

    关于PLINQ,以后将会介绍到,这里不会详细介绍,感兴趣的自行查阅资料。代码如下:

    01 using System;
    02 using System.Collections.Generic;
    03 using System.Linq;
    04 using System.Text;
    05 using System.Threading;
    06 using System.Threading.Tasks;
    07   
    08 namespace ConsoleApplication2
    09 {
    10     class Program
    11     {
    12         static void Main(string[] args)
    13         {
    14             int loops = 0;
    15             while (loops <= 100)
    16             {
    17                 long sum = 0;     
    18                 sum = Enumerable.Range(0, 1001).AsParallel().Sum();
    19                 System.Console.WriteLine(sum);
    20                 loops++;
    21             }
    22         }
    23     }
    24 }

    运行可以得到正确的结果。

    2.3、使用PLINQ——用ParallelEnumerable

    这个也不多说,直接上代码,因为关于PLINQ将在以后详细介绍,感兴趣的自行查阅资料。

    01 using System;
    02 using System.Collections.Generic;
    03 using System.Linq;
    04 using System.Text;
    05 using System.Threading;
    06 using System.Threading.Tasks;
    07   
    08 namespace ConsoleApplication2
    09 {
    10     class Program
    11     {
    12         static void Main(string[] args)
    13         {
    14             int loops = 0;
    15             while (loops <= 100)
    16             {
    17                 long sum = 0;
    18                 sum = ParallelEnumerable.Range(0, 1001).Sum(); 
    19                 System.Console.WriteLine(sum);
    20                 loops++;
    21             }
    22         }
    23     }
    24 }

    运行同样可以得到正确结果。

    2.4、使用Interlocked操作

    代码如下:

    01 using System;
    02 using System.Collections.Generic;
    03 using System.Linq;
    04 using System.Text;
    05 using System.Threading;
    06 using System.Threading.Tasks;
    07   
    08 namespace ConsoleApplication2
    09 {
    10     class Program
    11     {
    12         static void Main(string[] args)
    13         {
    14             int loops = 0;
    15             while (loops <= 100)
    16             {
    17                 long sum = 0;
    18                 Parallel.For(1, 1001, delegate(long i)
    19                 {
    20                     Interlocked.Add(ref sum, i);
    21                 });
    22                 System.Console.WriteLine(sum);
    23                 loops++;
    24             }
    25   
    26         }
    27     }
    28 }

    运行可以得到正确结果。

    2.5、使用Parallel.For的有Thread-Local变量重载函数

    这个方法已经在1.2中介绍,这里直接上代码,代码如下:

    01 using System;
    02 using System.Collections.Generic;
    03 using System.Linq;
    04 using System.Text;
    05 using System.Threading;
    06 using System.Threading.Tasks;
    07   
    08 namespace ConsoleApplication2
    09 {
    10     class Program
    11     {
    12         static void Main(string[] args)
    13         {
    14             int loops = 0;
    15             while (loops <= 100)
    16             {
    17                 int sum = 0;
    18                 Parallel.For(0, 1001, () => 0, (i, state,subtotal) =>
    19                 {
    20                     subtotal += i;
    21                     return subtotal;
    22                 },
    23                 partial => Interlocked.Add(ref sum, partial));
    24   
    25                 System.Console.WriteLine(sum);
    26                 loops++;
    27             }
    28   
    29         }
    30     }
    31 }

    运行可得正确结果。

    3、性能比较

    上面的解决方案那个比较好呢?请大家各抒己见!关于这个我已经测试了一下。

    PS:感觉写这篇的时候很累,思绪也很乱,不知大家对这篇还满意(⊙_⊙)?有什么地方需要改进,或者说不易理解,或者哪个地方错了!


    作者:水木    
     
  • 相关阅读:
    用 Mac 给树莓派重装系统
    Python:matplotlib 中文乱码的解决方案
    Python: 通过 pip 安装第三方包后依然不能 import
    Python: 安装 sklearn 包出现错误的解决方法
    grub2引导安装kali2.0及安装ibus拼音输入法
    2015移动安全挑战赛 第一题
    Linux kali 3.14-kali1-amd64 编译安装 wine 1.7.33
    git clone https协议问题
    常见问题
    Sieve_of_Eratosthenes
  • 原文地址:https://www.cnblogs.com/hsapphire/p/1915322.html
Copyright © 2011-2022 走看看