【转】【C#】【Thread】【Parallel】并行计算
并行计算
沿用微软的写法,System.Threading.Tasks.Parallel类,提供对并行循环和区域的支持。 我们会用到的方法有For,ForEach,Invoke。
- Program.Data = new List<int>();
- for (int i = ; i < ; i++)
- {
- Data.Add(i);
- }
下面我们定义4个方法,分别为for,foreach,并行For,并行ForEach。并测试他们的运行时长。
- /// <summary>
- /// 是否显示执行过程
- /// </summary>
- public bool ShowProcessExecution = false;
- /// <summary>
- /// 这是普通循环for
- /// </summary>
- private void Demo1()
- {
- List<int> data = Program.Data;
- DateTime dt1 = DateTime.Now;
- for (int i = ; i < data.Count; i++)
- {
- Thread.Sleep();
- if (ShowProcessExecution)
- Console.WriteLine(data[i]);
- }
- DateTime dt2 = DateTime.Now;
- Console.WriteLine("普通循环For运行时长:{0}毫秒。", (dt2 - dt1).TotalMilliseconds);
- }
- /// <summary>
- /// 这是普通循环foreach
- /// </summary>
- private void Demo2()
- {
- List<int> data = Program.Data;
- DateTime dt1 = DateTime.Now;
- foreach (var i in data)
- {
- Thread.Sleep();
- if (ShowProcessExecution)
- Console.WriteLine(i);
- }
- DateTime dt2 = DateTime.Now;
- Console.WriteLine("普通循环For运行时长:{0}毫秒。", (dt2 - dt1).TotalMilliseconds);
- }
- /// <summary>
- /// 这是并行计算For
- /// </summary>
- private void Demo3()
- {
- List<int> data = Program.Data;
- DateTime dt1 = DateTime.Now;
- Parallel.For(, data.Count, (i) =>
- {
- Thread.Sleep();
- if (ShowProcessExecution)
- Console.WriteLine(data[i]);
- });
- DateTime dt2 = DateTime.Now;
- Console.WriteLine("并行运算For运行时长:{0}毫秒。", (dt2 - dt1).TotalMilliseconds);
- }
- /// <summary>
- /// 这是并行计算ForEach
- /// </summary>
- private void Demo4()
- {
- List<int> data = Program.Data;
- DateTime dt1 = DateTime.Now;
- Parallel.ForEach(data, (i) =>
- {
- Thread.Sleep();
- if (ShowProcessExecution)
- Console.WriteLine(i);
- });
- DateTime dt2 = DateTime.Now;
- Console.WriteLine("并行运算ForEach运行时长:{0}毫秒。", (dt2 - dt1).TotalMilliseconds);
- }
下面是运行结果:
这里我们可以看出并行循环在执行效率上的优势了。
结论1:在对一个数组内的每一个项做单独处理时,完全可以选择并行循环的方式来提升执行效率。
原理1:并行计算的线程开启是缓步开启的,线程数量1,2,4,8缓步提升。(不详,PLinq最多64个线程,可能这也是64)
二、 并行循环的中断和跳出
当在进行循环时,偶尔会需要中断循环或跳出循环。下面是两种跳出循环的方法Stop和Break,LoopState是循环状态的参数。
- /// <summary>
- /// 中断Stop
- /// </summary>
- private void Demo5()
- {
- List<int> data = Program.Data;
- Parallel.For(, data.Count, (i, LoopState) =>
- {
- if (data[i] > )
- LoopState.Stop();
- Thread.Sleep();
- Console.WriteLine(data[i]);
- });
- Console.WriteLine("Stop执行结束。");
- }
- /// <summary>
- /// 中断Break
- /// </summary>
- private void Demo6()
- {
- List<int> data = Program.Data;
- Parallel.ForEach(data, (i, LoopState) =>
- {
- if (i > )
- LoopState.Break();
- Thread.Sleep();
- Console.WriteLine(i);
- });
- Console.WriteLine("Break执行结束。");
- }
执行结果如下:
结论2:使用Stop会立即停止循环,使用Break会执行完毕所有符合条件的项。
三、并行循环中为数组/集合添加项
上面的应用场景其实并不是非常多见,毕竟只是为了遍历一个数组内的资源,我们更多的时候是为了遍历资源,找到我们所需要的。那么请继续看。
下面是我们一般会想到的写法:
- private void Demo7()
- {
- List<int> data = new List<int>();
- Parallel.For(, Program.Data.Count, (i) =>
- {
- if (Program.Data[i] % == )
- data.Add(Program.Data[i]);
- });
- Console.WriteLine("执行完成For.");
- }
- private void Demo8()
- {
- List<int> data = new List<int>();
- Parallel.ForEach(Program.Data, (i) =>
- {
- if (Program.Data[i] % == )
- data.Add(Program.Data[i]);
- });
- Console.WriteLine("执行完成ForEach.");
- }
看起来应该是没有问题的,但是我们多次运行后会发现,偶尔会出现错误如下:
这是因为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的最基本的操作。
- /// <summary>
- /// 并行循环操作集合类,集合内只取5个对象
- /// </summary>
- private void Demo7()
- {
- ConcurrentQueue<int> data = new ConcurrentQueue<int>();
- Parallel.For(, Program.Data.Count, (i) =>
- {
- if (Program.Data[i] % == )
- data.Enqueue(Program.Data[i]);//将对象加入到队列末尾
- });
- int R;
- while (data.TryDequeue(out R))//返回队列中开始处的对象
- {
- Console.WriteLine(R);
- }
- Console.WriteLine("执行完成For.");
- }
- /// <summary>
- /// 并行循环操作集合类
- /// </summary>
- private void Demo8()
- {
- ConcurrentStack<int> data = new ConcurrentStack<int>();
- Parallel.ForEach(Program.Data, (i) =>
- {
- if (Program.Data[i] % == )
- data.Push(Program.Data[i]);//将对象压入栈中
- });
- int R;
- while (data.TryPop(out R))//弹出栈顶对象
- {
- Console.WriteLine(R);
- }
- Console.WriteLine("执行完成ForEach.");
- }
ok,这里返回一个序列的问题也解决了。
结论3:在并行循环内重复操作的对象,必须要是thread-safe(线程安全)的。集合类的线程安全对象全部在System.Collections.Concurrent命名空间下。
四、返回集合运算结果/含有局部变量的并行循环
使用循环的时候经常也会用到迭代,那么在并行循环中叫做 含有局部变量的循环 。下面的代码中详细的解释。
- /// <summary>
- /// 具有线程局部变量的For循环
- /// </summary>
- private void Demo9()
- {
- List<int> data = Program.Data;
- long total = ;
- //这里定义返回值为long类型方便下面各个参数的解释
- Parallel.For<long>(, // For循环的起点
- data.Count, // For循环的终点
- () => , // 初始化局部变量的方法(long),既为下面的subtotal的初值
- (i, LoopState, subtotal) => // 为每个迭代调用一次的委托,i是当前索引,LoopState是循环状态,subtotal为局部变量名
- {
- subtotal += data[i]; // 修改局部变量
- return subtotal; // 传递参数给下一个迭代
- },
- (finalResult) => Interlocked.Add(ref total, finalResult) //对每个线程结果执行的最后操作,这里是将所有的结果相加
- );
- Console.WriteLine(total);
- }
- /// <summary>
- /// 具有线程局部变量的ForEach循环
- /// </summary>
- private void Demo10()
- {
- List<int> data = Program.Data;
- long total = ;
- Parallel.ForEach<int, long>(data, // 要循环的集合对象
- () => , // 初始化局部变量的方法(long),既为下面的subtotal的初值
- (i, LoopState, subtotal) => // 为每个迭代调用一次的委托,i是当前元素,LoopState是循环状态,subtotal为局部变量名
- {
- subtotal += i; // 修改局部变量
- return subtotal; // 传递参数给下一个迭代
- },
- (finalResult) => Interlocked.Add(ref total, finalResult) //对每个线程结果执行的最后操作,这里是将所有的结果相加
- );
- Console.WriteLine(total);
- }
结论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的简单代码
- /// <summary>
- /// PLinq简介
- /// </summary>
- private void Demo11()
- {
- var source = Enumerable.Range(, );
- //查询结果按source中的顺序排序
- var evenNums = from num in source.AsParallel().AsOrdered()
- where num % ==
- select num;
- //ForAll的使用
- ConcurrentBag<int> concurrentBag = new ConcurrentBag<int>();
- var query = from num in source.AsParallel()
- where num % ==
- select num;
- query.ForAll((e) => concurrentBag.Add(e * e));
- }
上面代码中使用了ForAll,ForAll和foreach的区别如下:
PLinq的东西很繁杂,但是都只是几个简单的方法,熟悉下方法就好了。
原文地址:http://www.cnblogs.com/sorex/archive/2010/09/16/1828214.html
【转】【C#】【Thread】【Parallel】并行计算的更多相关文章
- 异步和多线程,委托异步调用,Thread,ThreadPool,Task,Parallel,CancellationTokenSource
1 进程-线程-多线程,同步和异步2 异步使用和回调3 异步参数4 异步等待5 异步返回值 5 多线程的特点:不卡主线程.速度快.无序性7 thread:线程等待,回调,前台线程/后台线程, 8 th ...
- 多线程之任务: Task 基础, 多任务并行执行, 并行运算(Parallel)
Task - 基于线程池的任务(在 System.Threading.Tasks 命名空间下) 多 Task 的并行执行 Parallel - 并行计算(在 System.Threading.Task ...
- 重新想象 Windows 8 Store Apps (43) - 多线程之任务: Task 基础, 多任务并行执行, 并行运算(Parallel)
[源码下载] 重新想象 Windows 8 Store Apps (43) - 多线程之任务: Task 基础, 多任务并行执行, 并行运算(Parallel) 作者:webabcd 介绍重新想象 W ...
- .Net进阶系列(13)-异步多线程(Task和Parallel)(被替换)
一. Task开启多线程的三种形式 1. 利用TaskFactory下的StartNew方法,向StartNew传递无参数的委托,或者是Action<object>委托. 2. 利用Tas ...
- C# IEnumerable,Lambda表达式和 Parallel并行编程的用法
以前一直主要做C++和C方面的项目,对C#不太了解熟悉,但听说不难,也就一直没有在意学习C#方面的知识.今天有个C#项目,需要做些应用的扩展,同时修改一些bug.但看了C#代码,顿时觉得有些不适应了. ...
- .Net进阶系列(10)-异步多线程综述(被替换)
一. 综述 经过两个多个周的整理,异步多线程章节终于整理完成,如下图所示,主要从基本概念.委托的异步调用.Thread多线程.ThreadPool多线程.Task.Parallel并行计算.async ...
- c#异步多线程
1.asyncrel = delegate.BeginInvoke实现委托异步调用. 2.异步等待 asyncrel.IsCompleted用于判断是否执行完毕 or EndInvoke用于等待执行完 ...
- Hadoop学习路线图
Hadoop家族产品,常用的项目包括Hadoop, Hive, Pig, HBase, Sqoop, Mahout, Zookeeper, Avro, Ambari, Chukwa,新增加的项目包括, ...
- testng教程之testng.xml的配置和使用,以及参数传递
昨天学习了一下testng基础教程,http://www.cnblogs.com/tobecrazy/p/4579414.html 昨天主要学习的是testng 的annotation基本用法和生命周 ...
- C#使用读写锁三行代码简单解决多线程并发写入文件时线程同步的问题
(补充:初始化FileStream时使用包含文件共享属性(System.IO.FileShare)的构造函数比使用自定义线程锁更为安全和高效,更多内容可点击参阅) 在开发程序的过程中,难免少不了写入错 ...
随机推荐
- Spring(七)持久层
一.Spring对DAO的支持 DAO:Data Access Object Spring提供了DAO框架,让开发人员无须耦合特定的数据库技术,就能进行应用程序的开发. Spring封闭了操作Orac ...
- 【原】UIView实现点击着重效果的解决方案
我们知道,在IOS中UIButton UIControl都有一个默认的选中效果,即点中后会图标会变暗,移开后又恢复正常.如何让UIView UIImageView等这些普通的view也实现同样的效果呢 ...
- 关于IOS多线程的好的技术博客
点击图片就能跳转链接到目标网页. 1.GCD<本人已阅,写的非常好>
- IOS之未解问题--给UITableView提取UITableViewDataSource并封装瘦身失败
前言:阅读了<更轻量的 View Controllers>,发现笔者这个优化重构代码的想法真的很不错,可以使得抽取的UITableViewDataSource独立写在一个类文件里,并且也写 ...
- python arguments *args and **args ** is for dictionaries, * is for lists or tuples.
below is a good answer for this question , so I copy on here for some people need it By the way, the ...
- 利用 cos 组件实现jsp中上传附件
需求:在web功能中附件上传功能为最基本的功能之一,所以用cos组件做了一个附件上传的demo.附件上传功能的实现可以利用其它的java组件实现,相关资料网上比较多. 说明步骤:下载组件并安装 --& ...
- SQLServer中char与varchar的区别
今天写一个项目的用户登录部分 刚开始做,所以是数据库和程序一起写 一开始没注意 在定义表T_Person时吧PerID和PerPwd的类型设定都是char(20) 并且写入了几个数据,诸如 id:01 ...
- 观察者模式--java jdk中提供的支持
一.简介 观察者设计模式有如下四个角色 抽象主题角色:把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者.抽象主题提供一个接口,可以增加和删除观察者角色.一般用一个抽象 ...
- 使用jsonp进行跨域访问
一.使用场景 当我们请求非本服务器的资源的时候,浏览器会禁止访问,并提示不允许跨域访问.此时我们可以使用jsonp这种请求方式,从其他服务器获取资源.在客户端调用提供jsonp支持的接口,获取json ...
- MyEclipse10 离线图文安装SVN插件教程
一.下载SVN插件subclipse 1.下载 下载地址:http://subclipse.tigris.org/servlets/ProjectDocumentList?folderID=2240 ...