• 初识Parallel Extensions之TPL


     

    初识Parallel ExtensionsTPL

    LazyBee

    上一篇我们介绍了Parallel Extensions中的PLINQ(具体请参考:初识Parallel ExtensionsPLINQ,今天我们来看看Parallel Extensions的另外一个组成部分任务并行库TPL(Task Parallel Library),TPL也是Parallel FX的关键组件之一。TPL的设计的目的是为了开发人员能够非常简单的使用TPL来编写可自动使用多处理器的托管代码,以提高程序运行的速度。PLINQ实现了以声明的方式(使用AsParallel)对数据源进行并行化的查询,而TPL是通过命令的方式将面向数据的操作(如for,foreach循环)以及轻量级的任务自动的运行在并行硬件上。

    如果我们希望在我们程序中使用TPL的话,和PLINQ一样,首先我们需要引用System.Threading.dll文件,由于TPL的类主要是放在System.ThreadingSystem.Threading.Tasks命名空间中,所以需要在我们的类文件中用using关键字将这两个命名空间添加进去。下面我们就将我们的关注点放在这两个命名空间。

    结构化并行性

    最普通的面向数据的操作就是循环,将循环并行化是并行程序的关键点之一。TPL提供了For, ForEachDo三个命令来将循环并行化。这三个命令都是System.Threading.Parallel类的静态方法。下面我们就来详细看一下这三个静态方法:

    For

    矩阵相乘,大家在学校的《线性代数》课上肯定都学过,不过到现在肯定都忘的光光的了,我也是现上网去搜了搜(呵呵,寒)。也就是说一个MXN的矩阵去乘以一个NXP的矩阵,就可以得到一个MXP的矩阵。其公式是: 。例如:


    下面我们就看看使用C#实现的矩阵相乘:

    class Matrix<T>
    {
    private T[,] _values;
         
    public Matrix(int rows, int columns)
         
    {
             
    if (rows < 1throw new ArgumentOutOfRangeException("rows");
             
    if (columns < 1throw new ArgumentOutOfRangeException("columns");
             Rows 
    = rows;
             Columns 
    = columns;
             _values 
    = new T[rows, columns];
          }

          
    public T this[int row, int column]
          
    {
              
    get return _values[row, column]; }
              
    set { _values[row, column] = value; }
          }

          
    public int Rows getprivate set; }
          
    public int Columns getprivate set; }
    }

    public static Matrix<double> MultiplySequential(Matrix<double> m1, Matrix<double> m2)
    {
    Matrix
    <double> result = new Matrix<double>(m1.Rows, m2.Columns);
         
    for (int i = 0; i < m1.Rows; i++)
         
    {
             
    for (int j = 0; j < m2.Columns; j++)
              
    {
                 result[i, j] 
    = 0;
                 
    for (int k = 0; k < m1.Columns; k++)
                 
    {
                      result[i, j] 
    += m1[i, k] * m2[k, j];
                  }

               }

         }

         
    return result;
     }

    有可能要进行相乘矩阵可能非常大(比如说500X500),我们可以利用TPL为我们提供Parallel.For来加快我们的计算速度,其定义为:

    public static void For(int fromInclusive,int toExclusive,   Action<int> body)

    其中:fromInclusive是循环的起始值

          toExclusive:循环的结束值

    Action<int>:每一次循环要执行的操作,该操作有一个int的输入参数,也就是循环

    的索引,没有返回值。

     

    将循环并行化的矩阵相乘的代码如下:

    public static Matrix<double> MultiplyParallel(Matrix<double> m1, Matrix<double> m2)
    {
    Matrix
    <double> result = new Matrix<double>(m1.Rows, m2.Columns);
        Parallel.For(
    0, m1.Rows, i =>
        
    {
            
    for (int j = 0; j < m2.Columns; j++)
            
    {
                 result[i, j] 
    = 0;
                 
    for (int k = 0; k < m1.Columns; k++)
                 
    {
                     result[i, j] 
    += m1[i, k] * m2[k, j];
                  }

             }

        }
    );
        
    return result;
    }

    在我的机器上两个随机产生的500X500double的矩阵相乘,非并行化版本与并行化版本运行的时间比平均为1.98.效果还是比较明显的,大家也可以在自己的机器上试试。

        同时,针对不同的情况Parallel.For也提供了重载版本。比如说我们需要在一个非常大的数据列表中查找指定的数据,在找到第一个数据之后就退出查找循环,这时候我们就可以使用下面这个重载版本:

    public static void For(int fromInclusive,int toExclusive,Action<int, ParallelState> body)

    前面两个参数也是循环的起始值和结束值。不同的就是每次循环要执行的操作,该操作多了一个ParallelState类型的输入参数,ParallelState有一个Stop方法,用于终止循环。

        如果我们需要计算指定范围内的所有素数之和,这是我们可以利用Parallel.For的另外一个重载:

    public static void For<TLocal>(int fromInclusive,int toExclusive,

    Func<TLocal> threadLocalSelector,

                                        Action<int, ParallelState<TLocal>> body,

                                        Action<TLocal> threadLocalCleanup)

    前面两个参数同样是循环的起始值和结束值,threadLocalSelector是产生本地状态的函数,body是每次循环(迭代)要进行的处理操作,threadLocalCleanup是用于清理线程本地状态的操作。这时我们计算素数之和的代码片段如下:

    int sum = 0;
    Parallel.For(
    010000, () => 0, (i,state)=>
    {
        
    if (isPrime(i)) state.ThreadLocalState += i;
    }
    ,
    partialSum 
    => Interlocked.Add(ref sum, partialSum));

    ()=>0就是告诉编译器state.ThreadLocalState中存放的数据类型是int型,并且将其初始化为0,在执行完循环之后,将所有线程中的ThreadLocalState的值相加来得到最终的素数之和。例子中partialSum就是表示ThreadLocalState的参数。
    你也可以使用这个重载来取出所有的素数(当然你也可以使用PLINQ来做):

    List<int> results = new List<int>();
    Parallel.For(
    010000, () => new List<int>(),(i, state) =>
                      
    {
                         
    if (isPrime(i)) state.ThreadLocalState.Add(i);
                       }
    ,
                       partialResults 
    =>
                       
    {
                         
    lock (results)
                         
    {
                             results.AddRange(partialResults);
                          }

                       }
    );
    注:这里isPrime是判断是否是素数的函数。(未完待续)
  • 相关阅读:
    uva10285 Longest Run on a Snowboard(DP)
    typecho 0.8 营销引擎
    新浪博客营销插件
    忍者X3备份说明
    QQ空间、说说抓取引擎
    yiqicms发布插件的使用
    SHOPEX v4.85 发布插件
    ecshop2.73插件使用帮助
    Destoon V5 发布插件
    Wordpress3.52营销引擎
  • 原文地址:https://www.cnblogs.com/LazyBee/p/1116879.html
Copyright © 2020-2023  润新知