自VS2010发布近半年了,虽然整天想学习新东西,要更新到自己时,发现原来自己基本也很懒,2008还没用上多久呢,无奈被2010了。用了几天,IDE模样还是和05、08差不多,加了些小特性,以后慢慢体验吧,第一感觉启动速度慢多了。主要还是.Net 4.0的变化,其实也就是修修补补,语言特性几乎没什么新特性,C#多了个Dynamic,十年前VB就支持的晚绑定。只好把注意力放在了Framework上,新加的并行支持应该是最大的变化吧。
VS2010发布会我也去过的,并行支持是一大卖点。当时记得台上一个MM对一个Linq查询语句加了个AsParallel(),性能就神奇地提高了一倍,台下掌声雷动。确实不费吹灰之力提高程序性能,是最能引起大家兴趣的。在看电子期刊时,看到冷冷同学,还有吴秦的文章,给偶这些菜鸟以震撼的启发,原来偶已经远远落在了在读大学生的后面。
那就开始学吧,就拿Parallel开刀。先抓个垫背的:
1 |
static void Set (int length)
|
3 |
var array = new int[length, length, length];
|
4 |
for (int i = 0; i < length; i++)
|
5 |
for (int j = 0; j < length; j++)
|
6 |
for (int k = 0; k < length; k++)
|
7 |
array[i, j, k] = System.Threading.Thread.CurrentThread.ManagedThreadId;
|
再请出真神:
01 |
static void ParallelSet(int length) |
03 |
var array = new int[length, length, length];
|
04 |
Parallel. For (0, length, i =>
|
06 |
for (int j = 0; j < length; j++)
|
07 |
for (int k = 0; k < length; k++)
|
08 |
array[i, j, k] = System.Threading.Thread.CurrentThread.ManagedThreadId;
|
PK:
1 |
CodeTimer.Time( "Single thread" , 100, () => Set (100));
|
2 |
CodeTimer.Time( "Multiple thread" , 100, () => ParallelSet(100));
|
结果,1136ms:729ms,果然不错。不过MSDN的例子说不定是被和谐过的,所以偶总会变变试验过程。果然发现另有乾坤。
<!-- 文章title -->
<!-- 作者信息等 -->
作者:
XiaoMing 发布时间: 2010-09-13 17:53 阅读: 413 次
原文链接 全屏阅读 [收藏] <!-- 页码和简介 -->
编辑点评:文中作者深入浅出的介绍了.Net4.0的并行编程,利用.Net 4.0的并行库实际编写了几段代码,供大家学习参考。
[1]
.Net 4.0并行库实用性演练[2] .Net 4.0并行库实用性演练
[3]
.Net 4.0并行库实用性演练
前面说在练习Parallel时,发现另有乾坤,是这样的代码:
01 |
static IEnumerable<Person> testFill() |
03 |
var list = new List<Person>(9);
|
04 |
Enumerable.Range(1, 99999).ToList().ForEach(n =>
|
06 |
var name = "Person" + n % 9;
|
07 |
list.Add(new Person { Id = n, Name = name });
|
09 |
Console.WriteLine( "Person's count is {0}" , list.Count);
|
12 |
static IEnumerable<Person> testFillParallel() |
14 |
var list = new List<Person>(9);
|
15 |
Enumerable.Range(1, 99999).AsParallel().ForAll(n =>
|
17 |
var name = "Person" + n % 9;
|
18 |
list.Add(new Person { Id = n, Name = name });
|
20 |
Console.WriteLine( "Person's count is {0}" , list.Count);
|
25 |
internal int Id { get; set; }
|
26 |
internal string Name { get; set; }
|
试验结果如下(单位ms):
次数
|
1
|
2
|
3
|
4
|
Fill 方法
|
37
|
27
|
26
|
26
|
FillParallel 方法
|
43
|
20
|
19
|
20
|
这个结果有点奇妙的。第一次多线程居然还不如单线程快,和上文例子比较一下,有点明白了。稍微改了下代码,在Add语句前加了个Thread.Sleep(1),并把 List<Person>集合元素减为999,试了一次,结果如下(单位ms):
次数
|
1
|
2
|
3
|
4
|
Fill 方法
|
1012
|
998
|
998
|
999
|
FillParallel 方法
|
547
|
504
|
504
|
504
|
多个线程协同工作时,分配任务本身有开销,要是分配的开销比任务本身还大,多线程就没有意义了。就比如你交待别人做某件事,要是交待的功夫比自己做还长,还不如自己做。不过从结果也可以看出一个辩证关系,从长远打算,第一次让别人熟悉业务,付出点培训成本,执行完一次后,以后就轻松多了,速度提高了一倍。如果这里Sleep一下,模拟长一点的单次处理过程,一开始多线程的优势就会非常明显。
FillParallel方法,大家觉得有没有其它问题呢?想必一般人都能看出,这里有最初级的线程安全问题。没看出的应该是刚学.Net各种集合的初学者,线程安全对他们还只是个太虚幻境。不过借助这个Parallel,就可以轻松神游幻境。把FillParallel方法循环一百次执行,会发现返回结果本来应该有999个元素,输出的却显示却结果经常少十几二十个。如果创建List时赋的容量不够,在List扩容时,还可能引发异常。一般是像下图这样(不过一百次都是999也不是不可能,要看你的RP了):
应提醒一点的是,试验要在Release编译模式下运行,不然看不到线程安全问题,并行执行的效率提升得也很有限。我用的电脑都是双核,不知道在单核电脑的运行情况如何,可能有一定区别。
接着我改下逻辑,增加了一个是否Person存在重名的判断,变成:
01 |
static IEnumerable<Person> testFillParallel() |
03 |
var list = new List<Person>(9);
|
04 |
Enumerable.Range(1, 999).AsParallel().ForAll(n =>
|
06 |
var name = "Person" + n % 9;
|
07 |
if (list.Count(p => p.Name == name) < 1) list.Add(new Person { Id = n, Name = name });
|
09 |
Console.WriteLine( "Person's count is {0}" , list.Count);
|
RP不管用了,执行几次,必抛异常:System.InvalidOperationException: Collection was modified; enumeration operation may no execute.
一个线程在枚举集合元素,这时必须保证集合不被其它线程修改,怎么办呢?以前,就知道用锁,现在据说有了线程安全的集合类,在System.Collections.Concurrent命名空间下,有ConcurrentDictionary, ConcurrentQueue, ConcurrentStack,就是没有ConcurrentList。费了半天,才发现与List对应的应该是BlockingCollection。
把集合定义换成:var list = new BlockingCollection<Person>(9); 只见刷刷刷,哪怕执行几万次都可以一路跑完了。
不过这样做,还是会发现问题,不知大家看出了吗?
接着上一次说,即使用了新的线程安全的集合BlockingCollection,这段代码还是会有问题。
01 |
static void testFillParallel() |
03 |
var list = new BlockingCollection<Person>(9999);
|
04 |
Enumerable.Range(1, 99999).AsParallel().ForAll(n =>
|
06 |
var name = "Person " + n % 9;
|
07 |
if (list.Count(p => p.Name == name) < 1) list.Add(new Person { Id = n, Name = name });
|
09 |
Console.WriteLine( "Person's count is {0}" , list.Count);
|
代码逻辑就是根据序号生成一个名字,并将名字不重复的人加入集合中。显然,最后集合中应该有9个人,应该很简单吧。执行一下,请看结果:
可见,绝大多数结果都是正确,只有两次执行出现了异常,在正式系统运行中,这可是要命的两条!难道是BlockingCollection的问题吗?细想不太可能,微软怎么也不会漏过这么明显的设计问题,实际上是自己对线程安全认识不准确。集合元素之所以偶尔会多一个,是这样的情况:线程A抢先一步,占住集合判断是否存在这个人名,线程B被BlockingCollection拦在外面;A发现集合中查无此人,正想加一个,然而不知怎么回事,它没马上继续,就好像龟兔赛跑,兔子要到终点了,做了个白日梦,梦一醒自己成了老二。B趁机赶上,正好锁也解了,碰巧它查的人也没有,一鼓作气跑完全程。这时A跑了一大半,岂肯甘心,赖皮着到终点,不管三七二十一,有没有和B刚才加的重复,硬把自己塞了进去。
这样理解,也容易解释,第一次执行出现异常结果概率很高,因为开始时线程间步调几乎完全一致,刚才故事的前半段最有可能上演。另外条件判断时间越久,异常结果出现概率越小,比如把name = "Person "+ n % 9改成name = "Person "+ n % 50,这时A就是做白日梦,B也全力追赶,无奈前面被落后太多,一千次中只有一两次结果异常。
其实System.Collections.ConCurrent命名空间底下的类,只对多线程环境下的某次访问保证健壮性,却不能保证多线程下,作为业务对象的业务操作的准确性,实际上也无法做到。然而话说回来,我们使用这些类,是让它们在凶险的多线程战场上,为我们奋勇杀敌,而不是明哲保身的。如果业务出错了,仗都打输了,集合再线程安全亦无济于事。所以,不要让线程安全误导我们,要着眼在业务上,业务安全实现了,自然线程安全自然不在话下。
现在要解决结果异常,自然又想到了老办法—上锁,先保证万无一失。显然,并行集合也不必上场了,还是用List。为了接近真实场景,将集合最大元素数目提高到999,这样在判断新元素是否重复是要花较多时间,取代Thread.Sleep(1),代码如下:
01 |
static void testFillParallel() |
03 |
var list = new List<Person>(999);
|
05 |
Enumerable.Range(1, 9999).AsParallel().ForAll(n =>
|
07 |
var name = "Person " + n % 999;
|
10 |
if (list.Count(p => p.Name == name) < 1) list.Add(new Person { Id = n, Name = name });
|
13 |
Console.WriteLine( "Person's count is {0}" , list.Count);
|
测试结果如下:
次数
|
1
|
2
|
3
|
4
|
Fill 方法
|
304
|
292
|
291
|
292
|
FillParallel 方法
|
340
|
298
|
296
|
297
|
可见,虽然运用并行方式,也保证每个迭代有一定的执行时间,虽然加锁可以保证结果正确,但大多数时候,只有一个线程能进行工作,其他线程再多也只能等待,实际上还不如单线程,也失去了并行运算的意义。
学习.Net4.0 的并行库并非我们的目的,我们目的是解决现实的问题。解决关键,还是在锁上。锁是把双刃剑,我们要让线程占用锁时间尽可能短。在填加集合时,加锁没办法避免,但只是为遍历集合而加锁,好像是种浪费。根据三级锁协议,应该允许多个只读操作同时进行,可是实现IEnumerable的集合,都不允许在遍历访问时修改集合,既然如此,我们自己搞一个集合副本,只作判断用,如果原集合更新,副本也随之更新,不是就能解决同时遍历的问题了吗?
试验代码:
01 |
static void testFill() |
03 |
var list = new List<Person>(999);
|
05 |
Enumerable.Range(1, 9999).ToList().ForEach(n =>
|
07 |
var name = "Person " + n % 999;
|
08 |
if (list.Count(p => p.Name == name) < 1) list.Add(new Person { Id = n, Name = name });
|
10 |
Console.WriteLine( "Person's count is {0}" , list.Count);
|
12 |
static void testFillParallel() |
14 |
var list = new List<Person>(999);
|
16 |
var resultCopy = new Person[999];
|
17 |
bool hasNewMember = false;
|
18 |
Enumerable.Range(1, 9999).AsParallel().ForAll(n =>
|
20 |
var name = "Person " + n % 999;
|
21 |
if (resultCopy.Count(p => p!=null && p.Name == name) < 1)
|
28 |
if (resultCopy.Count(p => p != null && p.Name == name) > 0) return;
|
31 |
list.Add(new Person { Id = n, Name = name });
|
32 |
list.CopyTo(resultCopy);
|
37 |
Console.WriteLine( "Person's count is {0}" , list.Count);
|
试验结果(单位ms):
次数
|
1
|
2
|
3
|
4
|
Fill 方法(和上次一样)
|
309
|
291
|
292
|
292
|
FillParallel 方法
|
210
|
166
|
165
|
166
|
看来,自己总算写了第一段能发挥并行运算的代码。
当然,最后代码还有很多可改进的地方,比如list.CopyTo方法,还有发现有新成员后第二次判断,向让性能与CPU核数正比的目标努力。
还有,那个if(...){ lock(...){ if(...){的语句,跟单例模式的一种普遍实现很像,单例模式也是多线程环境下一种设计模式,也许其中有些异曲同工之处吧。
分享到:
相关推荐
本技术资源包括了我在博客上发表的“.NET 4.0并行计算技术基础”系列文章中的所有示例源码,并包含一个PDF,汇总了所有文章。 与作者互动请访问http://blog.csdn.net/bitfan/archive/2009/10/26/4728180.aspx 更多...
.NET 4.0引入的一个新概念---任务,作为支持并行运算的重要组成部分,同时,也作为对线程池的一个补充和完善。本资源文件中模拟一个插件的工作,在主线程中分配插件的任务,如果插件执行超时,则主线程中断任务,...
.NET 4.0的并行性功能介绍-PLINQ,TPL和Rx
在.net4.0中使用Parallel并行运算的示例
介绍.NET 4.0的并行编程功能。
中科院讲座 主要内容: 1.NET技术王国探秘 2并行计算的时代 3“拥抱变化”的软件系统 4“动静结合”的编程世界
压缩包中是本次讲座的PPT和示例源码,主要内容来自于我写的《.NET 4.0面向对象编程漫谈》之《应用篇》一书。——金旭亮 主要内容: .NET技术王国探秘 并行计算的时代 “拥抱变化”的软件系统 “动静结合”的编程...
.NET-4.0中的并行开发.docx
.Net 3.5 并行编程升级包,VS2008并行编程支持包.Net 3.5 并行编程升级包,VS2008并行编程支持包
.NET框架 4.0主要增加了并行支持,英文版于2010年4月12日推出。 企业基础 .NET提供开发软件的独立平台,自带高度安全的网络系统,相当倚重软件组件以及组件导向程序。在这方面它完全取代前者(COM)。
这是一个系列文章,系统介绍.NET 4.0 中引入的并行扩展(包括任务并行库TPL和PLINQ)开发技术。
TDF以任务并行库(TPL)提供的API和调度基础结构为基础,并与C#,Visual Basic和F#提供的异步语言支持集成在一起。发行版作者微软Seabiscuit( )支持的平台: .NET Framework 4.0 .NET Framework 4.5
完全重写的,没有任何第三方依赖项,并尽可能多地利用并行性来获得最佳性能。 特征 使用同步和异步方法执行 SSH 命令 返回命令执行退出状态等信息 为同步和异步操作提供 SFTP 功能。 提供SCP功能。 提供上传和下载 ...
NET4.0并行计算技术基础1-12 word版打包。
作为一个ASP.NET开发人员,在之前的开发经历中接触多线程编程的机会并不是很多,但是随着.NET 4.0的发布临近,我越来越感受到未来的1-2年中并行计算将会有很大的应用。于是决定通过写日志的方式来总结一下.NET 3.5下...
.net 4.0并行计算的书籍,还不错,市场上有中文版卖
设计模式.net并行编程.pdf 设计模式.net并行编程.pdf 设计模式.net并行编程.pdf