随笔 - 81  文章 - 0  评论 - 282 

1. 并行LINQ(PLINQ)

并行 LINQ (PLINQ) 是 LINQ 模式的并行实现。 PLINQ 查询在许多方面类似于非并行 LINQ to Objects 查询。 PLINQ 尝试充分利用系统中的所有处理器, 它利用所有处理器的方法是,将数据源分成片段,然后在多个处理器上对单独工作线程上的每个片段并行执行查询。 在许多情况下,并行执行意味着查询运行速度显著提高。

PLINQ有两个重要的类:ParallelEnumerable和ParallelQuery.其中, ParallelEnumerable包含许多在ParallelQuery类型上进行操作的扩展方法. ParallelEnumerable同样实现许多在上Enumerable的方法,因为 ParallelQuery 实现IEnumerable接口,使用我们可以很方便的创建ParallelQuery类的一个实例并运用在ParallelEnumerable上实现的并行方法.接下来,通过代码来学习这些重要的类.

 2. ParallelEnumerable 运算符

ParallelEnumerable包含很多运算符,他们对下封装很多复杂的实现过程, 以便对上我们可以方便的使用并行linq查询.具体如下:

(1) AsParallel

 PLINQ 的入口点。指定如果可能,应并行化查询的其余部分。PLINQ 通常只需向数据源添加 AsParallel 查询操作,即可在某些查询类型的旧版代码上获得显著的性能改进。代码如下:

int[] source = new int[1000];

            for (int i = 0; i < source.Length; i++)

            {

                source[i] = i;

            }

 var  presult = source.AsParallel().Select(c => Math.Pow(c, 3));

            foreach (var d in presult)

            {

                Console.WriteLine("AsParallel Result  is:{0}", d);

            }

通过上面的代码,我们可以通过使用AsParallel()扩展方法,即可开启并行查询.因此,一般的linq查询代码我们只需要使用AsParallel()扩展的方法,即可启用并行查询.

 

(2) AsSequential<TSource>

 指定查询按顺序运行,也就是将并行的查询转换成一般的顺序查询.代码如下:

            int[] source = new int[1000];

            for (int i = 0; i < source.Length; i++)

            {

                source[i] = i;

            }

 

            var result = source.AsParallel().AsSequential().Where(c => c > 0);

 

            foreach (var d in result)

            {

                Console.WriteLine("AsSequential Result:{0}", d);

            }

 

(3) AsOrdered

 指定 PLINQ 应保留查询的其余部分的源序列排序,直到例如通过使用 orderby子句更改排序为止。但是,因为并行查询的情况,默认未排序的,除非顺序特别重要,否则从性能角度的考虑建议不要排序.

            int[] sourceData = new int[5];

            for (int i = 0; i < sourceData.Length; i++)

            {

                sourceData[i] = i;

            }

          

            var results1 = sourceData.AsParallel().AsOrdered().Select(c => Math.Pow(c, 3));

          

            foreach (double d in results1)

            {

                Console.WriteLine("AsOrdered result {0} ", d);

            }

 

(4) AsUnordered<TSource>

 指定查询的其余部分的 PLINQ 不需要保留源序列的排序。

(5)  WithCancellation<TSource>

 指定 PLINQ 应定期监视请求取消时提供的取消标记和取消执行的状态,这样我们就可以在需要时候取消查询。代码如下:

            CancellationTokenSource token = new CancellationTokenSource();

             int[] sourceData = new int[1000000];

            for (int i = 0; i < sourceData.Length; i++)

            {

                sourceData[i] = i;

            }

 

            var results1 = sourceData.AsParallel().WithCancellation(token.Token).WithMergeOptions(ParallelMergeOptions.NotBuffered).Where(c => c > 1);

 

            Task.Factory.StartNew(() =>

            {

                Thread.Sleep(5000);

                token.Cancel();

                Console.WriteLine("Token source cancelled");

            });

          

            try

            {

                foreach (double d in results1)

                {

                    Console.WriteLine("CancellationTokenSource result {0},Thread Id:{1} ", d, Thread.CurrentThread.ManagedThreadId);

                }

            }

            catch (OperationCanceledException)

            {

                Console.WriteLine("Caught cancellation exception");

            }

            Console.WriteLine("Press enter to finish");

            Console.ReadLine();

 

(6) WithDegreeOfParallelism<TSource>

 指定 PLINQ 应当用来并行化查询的处理器的最大数目。并行度是将用于处理查询的同时执行的任务的最大数目. 代码如下:

  var results1 = sourceData.AsParallel().WithDegreeOfParallelism(3).Where(c => c > 1);

    我们不防想想,如果将WithDegreeOfParallelism设置为1,会是什么情况呢?

(7) WithMergeOptions<TSource>

 提供有关 PLINQ 应当如何(如果可能)将并行结果合并回到使用线程上的一个序列的提示, 指定查询对输出进行缓冲处理的方式.

   var results1 = sourceData.AsParallel().WithMergeOptions(ParallelMergeOptions.NotBuffered).Where(c => c > 1);

其中ParallelMergeOptions枚举可选择以下几个值:

Ø Default 使用默认合并类型,即 AutoBuffered。 

Ø NotBuffered 不利用输出缓冲区进行合并。一旦计算出结果元素,就向查询使用者提供这些元素。 

Ø AutoBuffered 利用系统选定大小的输出缓冲区进行合并。 在向查询使用者提供结果之前,会先将结果累计到输出缓冲区中。 

Ø FullyBuffered 利用整个输出缓冲区进行合并。 在向查询使用者提供任何结果之前,系统会先累计所有结果。 

(8) WithExecutionMode<TSource>

 指定 PLINQ 应当如何并行化查询(即使默认行为是按顺序运行查询)。

var results1 = sourceData.AsParallel().WithExecutionMode(ParallelExecutionMode.Default).Where(c => c > 1);

 其中, ParallelExecutionMode枚举可以选择如下两种值:

Ø Default 这是默认设置。 PLINQ 将检查查询的结构,仅在可能带来加速时才对查询进行并行化。 如果查询结构指示不可能获得加速,则 PLINQ 会将查询当作普通的 LINQ to Objects 查询来执行。 

Ø ForceParallelism 并行化整个查询,即使要使用系统开销大的算法。 如果认为并行执行查询将带来加速,则使用此标志,但处于默认模式的 PLINQ 将按顺序执行它。 

 

(9) ForAll<TSource>

 多线程枚举方法,与循环访问查询结果不同,它允许在不首先合并回到使用者线程的情况下并行处理结果。这是处理并行化查询输出的高效方法,因为它不需要在结束时执行合并步骤.代码如下:

  int[] sourceData = new int[5];

            for (int i = 0; i < sourceData.Length; i++)

            {

                sourceData[i] = i;

            }

 

            sourceData.AsParallel().ForAll((d) =>

            {

                Console.WriteLine("ForAll result {0} ", d);

            }

 

(10) Aggregate 重载

 对于 PLINQ 唯一的重载,它启用对线程本地分区的中间聚合以及一个用于合并所有分区结果的最终聚合函数。

3. 延迟执行

默认情况下PLINQ和LINQ类似,它只在需要查询结果时才计算.为了快速得到结果,我们可以在查询之后再调用ToArray(), ToDictionary(),ToList()方法,以便使其立刻计算.

4. 异常处理

异常的处理还是使用AggregateException类处理.具体内容可以查看之前的”异常处理”章节.

5. Range方法

ParallelEnumerable类还提供了Rang扩展方法. 它可生成指定范围内的整数的并行序列,也就是在指定的范围内进行并行处理.代码如下:

var result1 =

            from e in ParallelEnumerable.Range(0, 100)

            where e % 2 == 0

            select Math.Pow(e, 2);

6. Repeat方法

ParallelEnumerable类还提供了Repeat扩展方法. 生成包含一个重复值的并行序列,也就是并行生成重复值.代码如下:

var result2 =

            ParallelEnumerable.Repeat(10, 1000)

            .Select(item => Math.Pow(item, 2));

 

此次,有关PLINQ的基本操作介绍完毕,后续还有其他内容继续介绍.
 
     本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
 
 
posted on 2011-09-01 00:53 ♂风车车.Net 阅读(2669) 评论(23) 编辑 收藏

 回复 引用 查看   
#1楼 2011-09-01 08:12 | asxinyu      
大哥,好文啊。谢谢。。
 回复 引用 查看   
#2楼 2011-09-01 08:28 | 静至      
文是不错
但Linq这个技术倒是不怎么地,Linq已经够浪费数据资源,再来一个多线程,加上数据访问量高峰和服务器中挂有多个这个系统、网站,等于一个定时炸弹

 回复 引用 查看   
#3楼 2011-09-01 08:34 | 贺臣      
不错的文章
 回复 引用 查看   
#4楼 2011-09-01 08:44 | zsea      
@静至
这个不是Linq的错吧,难道一个搞软件的,不知道该在什么时候用什么技术?

 回复 引用 查看   
#5楼 2011-09-01 08:58 | 永不言败      
又有人评论linq不行了
 回复 引用 查看   
#6楼 2011-09-01 09:26 | 心火      
不错
 回复 引用 查看   
#7楼 2011-09-01 09:29 | 偶厚      
比较笼统.....能不能细说一下..谢谢
比如source.AsParallel().AsSequential().Where(c => c > 0);和
source.Where(c => c > 0);有区别吗?

AsOrdered能不能给举个并行和不并行的例子...

PLINQ 应定期监视请求取消时提供的取消标记和取消执行的状态,不是很明白,代码最好能加点注释....

谢谢

 回复 引用 查看   
#8楼 2011-09-01 10:00 | ryanding      
linq 很完美。性能方面在于你怎么去使用他。同时linq可以简化我们的代码。为何说linq不行呢?!
 回复 引用 查看   
#9楼 2011-09-01 10:06 | 小侠女      
@偶厚
source.Where(c => c > 0);是普通查询吧,不会使用到多核并行。而source.AsParallel().AsSequential().Where(c => c > 0);自然在条件允许的情况下要并行执行的了。

 回复 引用 查看   
#10楼 2011-09-01 16:14 | 天空海阔      
无法判断客户端机器是多核还是单核,怎么确定用哪种方法?
 回复 引用 查看   
#11楼 2011-09-02 09:07 | 静至      
引用zsea:
@静至
这个不是Linq的错吧,难道一个搞软件的,不知道该在什么时候用什么技术?

那你告诉什么时候可以使用Linq好么?
搞软件!!!不会不知道功能扩展是随时都会发生的么!!!
Linq无论是从小项目到大项目都不是很复合开发需要,由其是功能扩展方面,冗余太大,不信可以看看SQL server Profiles,Linq一但运行对SQL访问就会剧增
可能会想说简单开发个小项目不会有什么问题吧,错误!一时的偷懒只会带来更大冗余
而且Linq支持是.net 3.5以上,多数服务器只支持.net 2.0(由其手中资源很少的时间)
有的技术好可用,有的技术只是一个形像工程,看看知道怎么会事就可以

 回复 引用 查看   
#12楼 2011-09-02 09:21 | zsea      
@静至
1、告诉你是大度,不告诉你是本份。
2、你说的是Linq to Sql。
3、.Net又不是只有Linq,你不用就是了。何必要来吵着告诉大家呢,不用强迫别人同意你的观点嘛。

 回复 引用 查看   
#13楼 2011-09-02 09:27 | 静至      
引用zsea:
@静至
1、告诉你是大度,不告诉你是本份。
2、你说的是Linq to Sql。
3、.Net又不是只有Linq,你不用就是了。何必要来吵着告诉大家呢,不用强迫别人同意你的观点嘛。

兄弟不用急动
我还要告诉你更不幸的,我使Linq就会挨上级领导骂

 回复 引用 查看   
#14楼 2011-09-02 09:30 | zsea      
@静至
哈哈,领导至上,领导至上。
Linq to Sql少用。
普通的查询啊,什么的,可以用的。

 回复 引用 查看   
#15楼 2011-09-02 16:07 | testzhangsan      
有实用性吗
 回复 引用 查看   
#16楼[楼主] 2011-09-02 22:34 | ♂风车车.Net      
@偶厚
最终都顺序执行。
但是,source.AsParallel().AsSequential().Where多了中间的转换。

WithCancellation是提供了可以取消并行计算方式。也就是说,我们可以在某个时候取消正在执行的并行代码。

 回复 引用 查看   
#17楼[楼主] 2011-09-02 22:36 | ♂风车车.Net      
@testzhangsan
个人觉得,还是有用处的。虽然,现在可能写代码的时候,没注意。但是,说不定哪天就可以运用起来了。任何技术都有它存在的理由。

 回复 引用 查看   
#18楼[楼主] 2011-09-02 22:39 | ♂风车车.Net      
@静至
怎么说呢,LINQ在某些性能方面的确表现的不怎么样。但是,我觉得至少在某些时候使用起来很方便。
说性能一块,我认为,如果系统存在的性能问题,可能涉及到多方面的原因,要具体问题具体分析。

 回复 引用 查看   
#19楼[楼主] 2011-09-02 22:40 | ♂风车车.Net      
@天空海阔
不用你去判断。AsParallel()会自动在合适的时候去并行计算,如果不合适并行,那么就会单一的去处理,所以不用担心。

 回复 引用 查看   
#20楼 2011-11-30 12:28 | Men_y      
@静至
您好,我看不惯了,我从不留言的,今天看到我的说几句.
我们楼主讨论的是并行Linq.你说的是LinqToSql.LinqToSql性能是肯定不好的这点毋庸置疑实际项目中完全不会用它,可以说LinqToSql完全不复存在.但是除了Linq其实是toObject的.你有个数据源存在内存中需要筛选或者排序你不用Linq.那你慢慢去写循环写算法.ok

 回复 引用 查看   
#21楼 2011-11-30 16:03 | 静至      
引用Men_y:
@静至
您好,我看不惯了,我从不留言的,今天看到我的说几句.
我们楼主讨论的是并行Linq.你说的是LinqToSql.LinqToSql性能是肯定不好的这点毋庸置疑实际项目中完全不会用它,可以说LinqToSql完全不复存在.但是除了Linq其实是toObject的.你有个数据源存在内存中需要筛选或者排序你不用Linq.那你慢慢去写循环写算法.ok


LinqToSql我已经和LZ解释过,在使用LinqToSql时对LinqTo别的都有一点学习,我确实不会用它进行筛选和排序,因为我不知道它的源码是什么,而且引用Linq就会多出许多方法,不是每个程序员都会都能分清,如Count()和Count,难倒你想让我再开发之前对团队中每个人上一堂Linq课程么

 回复 引用 查看   
#22楼 2011-12-02 15:54 | 紫砂壶      
@Men_y
@静至
自己写点算法又如何会耽误很多时间吗?总比你从头学效率不高兼容性不好的所谓ef,linq,所需要的时间少点吧!再说了排序,筛选都是最基本的东西,如果这些都想通过工具实现那你还是别做开发了!!

 回复 引用 查看   
#23楼 2011-12-19 12:12 | doutt2000      
汗!linq to sql被抛弃的原因是性能差。那你写二进制还写汇编的啊?
估计你要说是写ado.net 。java之所以在企业开发强大的原因就是有成熟的orm、ioc等框架,说白都是很牺牲性能的,因为企业开发性能不是首要考虑的。除非你做单片机,呵呵。