.NET4.0并行计算技术基础(5)

 

3 使用任务并行库实现并行处理

         上面介绍了基于线程编码实现并行处理的技术要点,可以看到还是比较繁琐的。但通过使用 .NET 4.0 的并行库,可以简化开发工作。我们略微详细一点地介绍一下示例程序中是如何使用任务并行库实现并行计算的。

         其中的一个关键函数是 ForRange() 函数,先来看看它的声明:

 

       public static ParallelLoopResult ForRange(

        int fromInclusive, int toExclusive,

        Action<int, int> body )

        {   //… 代码略       }

 

         前两个参数代表要计算的数据在数组中的起始和结束索引,第 3 个参数是一个 Action 委托,它引用一个将被并行执行的处理函数。

         在并行计算程序中,任务的分解方式是一个需要仔细考虑的问题,有一种常用的方案就是依据本机所包容的处理器个数来决定并行处理的任务数,可以直接调用 .NET 基类库中的类来获取这一信息。

 

     int numberOfPartitions = System.Environment.ProcessorCount;

        

         确定了要分解的任务数,就可以算出每个子任务负责处理的数据项数:

 

          // 获取要计算的数据范围

            int range = toExclusive - fromInclusive;

            // 计算出每个并行任务要计算的数据个数

            int stride = range / numberOfPartitions;

            if (range == 0) numberOfPartitions = 0;

 

         现在到了关键的部分,我们不是使用线程来执行每个子任务,而是直接调用 .NET 4.0 任务并行库中的 Parallel 类来完成这一个工作:

 

            return Parallel.For(0, numberOfPartitions, i =>

            {

              int start = i * stride;

              int end = (i == numberOfPartitions - 1) ? toExclusive : start + stride;

             body(start, end);

            });

        }

 

         Parallel.For() 是一个静态方法,它的第 3 个参数是类型为 Action<int> 的委托,在这里,我们直接使用 Lambda 表达式来将一个函数直接“内联”作为 For() 方法的参数。

         For() 方法有一个 ParallelLoopResult 类型的返回值,可以通过此返回值的 IsCompleted 属性了解 For() 方法启动的所有任务是否运行结束。

 

      交叉链接:

       如果读者不熟悉ActionFunc 等系统预定义委托的含义及用法,请参看本书第12 章《微软的创造——委托揭秘》。

       Lambda 表达式则是.NET 3.0 引入的特性,与LINQ 技术关联紧密,相关内容请参看本书第24 章《统一的数据访问模式:LINQ 》。

 

         在示例程序中,通过以下代码调用上面定义好的 ForRange() 方法启动并行计算:

 

// ……代码略

double mean = CalcuateMeanInSequence(Data); // 计算平均值

 

// 启动并行计算

ForRange(0, Data.Length, (start, end) =>

            {

                double sum = 0;

                double temp = 0;

                for (int i = start; i < end; i++)

                {

                    temp = Data[i] - mean;

                    sum += temp * temp;

                }

                // 保存结果

                lock (SquareSumLockObject)

                {

                    SquareSumUsedByThread += sum;

                };

            });

// ……代码略

 

         注意对 ForRange() 方法调用的第 3 个参数又是一个 Lambda 表达式。建议读者一定要花费一点时间去习惯阅读和编写 Lambda 表达式,今后您一定会在许多场合看到类似的语法现象。

4 小结

         在本小节中,我们通过一个示例程序对比了“顺序算法”,“使用线程的并行算法”和“基于 TPL 的并行算法”三种编程方式。

         很明显,顺序算法最简单,最易于理解,直接使用线程实现并行计算的,需要编写的代码最多,调试起来也麻烦,而使用 TPL ,可以不需要直接地与线程打交道,除了语法上略微“奇怪”和“别扭”一些(呵呵,看多了就习惯了)之外,还是比较简洁的。

         从执行时间上看,顺序算法最快, TPL 次之,而使用线程的最慢。

         必须指出,程序运行的快慢与许多因素有关,比如计算机硬件配置,是否采用了优化算法,以及操作系统平台等,上述性能比较结果是在一台双核笔记本 +Windows 7 下得到的,读者的结果有可能不一样。

         从性能比较的结果来看,并行计算并非总具有性能优势,这也提醒我们要注意并行计算的应用场合:

         1 )每个数据项要执行的处理工作量很大,需要耗费较多的时间

         2 )要处理的数据集合很大。

         很明显,对于求方差这样一个任务而言,对于每个数据项要执行的操作实在太简单了,只不过是加加减减和乘乘除除,因此,串行算法的性能更优。

         另外,尽管 TPL 极大地简化了并行程序的开发门槛,在很多情况下不需要直接地与线程打交道,但仍然需要开发者掌握多线程开发的基础知识与基本技能: 比如示例使用 lock 来互斥地访问多线程共享的变量 SquareSumUsedByThread

         所以, 多线程技术是开发并行计算程序的基础

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值