详细的.Net并行编程高级教程--Parallel
一直觉得自己对并发了解不够深入,特别是看了《代码整洁之道》觉得自己有必要好好学学并发编程,因为性能也是衡量代码整洁的一大标准。而且在《失控》这本书中也多次提到并发,不管是计算机还是生物都并发处理着各种事物。人真是奇怪,当你关注一个事情的时候,你会发现周围的事物中就常出现那个事情。所以好奇心驱使下学习并发。便有了此文。
一、理解硬件线程和软件线程
多核处理器带有一个以上的物理内核--物理内核是真正的独立处理单元,多个物理内核使得多条指令能够同时并行运行。硬件线程也称为逻辑内核,一个物理内核可以使用超线程技术提供多个硬件线程。所以一个硬件线程并不代表一个物理内核;Windows中每个运行的程序都是一个进程,每一个进程都会创建并运行一个或多个线程,这些线程称为软件线程。硬件线程就像是一条泳道,而软件线程就是在其中游泳的人。
二、并行场合
.Net Framework4 引入了新的Task Parallel Library(任务并行库,TPL),它支持数据并行、任务并行和流水线。让开发人员应付不同的并行场合。
?数据并行:有大量数据需要处理,并且必须对每一份数据执行同样的操作。
比如通过256bit的密钥对100个Unicode字符串进行AES算法加密。
?任务并行:通过任务并发运行不同的操作。例如生成文件散列码,加密字符串,创建缩略图。
?流水线:这是任务并行和数据并行的结合体。
TPL引入了System.Threading.Tasks ,主类是Task,这个类表示一个异步的并发的操作,然而我们不一定要使用Task类的实例,可以使用Parallel静态类。它提供了 Parallel.Invoke, Parallel.For Parallel.Forecah 三个方法。
三、Parallel.Invoke
试图让很多方法并行运行的最简单的方法就是使用Parallel类的Invoke 方法。例如有四个方法:
?WatchMovie
?HaveDinner
?ReadBook
?WriteBlog
通过下面的代码就可以使用并行。
System.Threading.Tasks.Parallel.Invoke(WatchMovie, HaveDinner, ReadBook, WriteBlog);
这段代码会创建指向每一个方法的委托。Invoke方法接受一个Action的参数组。
1 p ublic static void Invoke(params Action[] actions);
用lambda表达式或匿名委托可以达到同样的效果。
System.Threading.Tasks.Parallel.Invoke(() => WatchMovie(), () => HaveDinner(), () => ReadBook(), delegate() { WriteBlog(); });
1.没有特定的执行顺序。
Parallel.Invoke方法只有在4个方法全部完成之后才会返回。它至少需要4个硬件线程才足以让这4个方法并发运行。但并不保证这4个方法能够同时启动运行,如果一个或者多个内核处于繁忙状态,那么底层的调度逻辑可能会延迟某些方法的初始化执行。
给方法加上延时,就可以看到必须等待最长的方法执行完成才回到主方法。
1.static void Main(string[] args)
2. {
3. System.Threading.Tasks.Parallel.Invoke(W
atchMovie, HaveDinner, ReadBook,
4. WriteBlog);
5. Console.WriteLine("执行完成");
6. Console.ReadKey();
7. }
8.
9.static void WatchMovie()
10. {
11. Thread.Sleep(5000);
12. Console.WriteLine("看电影");
13. }
14.static void HaveDinner()
15. {
16. Thread.Sleep(1000);
17. Console.WriteLine("吃晚饭");
18. }
19.static void ReadBook()
20. {
21. Thread.Sleep(2000);
22. Console.WriteLine("读书");
23. }
24.static void WriteBlog()
25. {
26. Thread.Sleep(3000);
27. Console.WriteLine("写博客");
28. }
这样会造成很多逻辑内核处于长时间闲置状态。
四、Parallel.For
Parallel.For为固定数目的独立For循环迭代提供了负载均衡 (即将工作分发到不同的任务中执行,这样所有的任务在大部分时间都可以保持繁忙) 的并行执行。从而能尽可能地充分利用所有的可用的内核。
我们比较下下面两个方法,一个使用For循环,一个使用Parallel.For 都是生成密钥在转换为十六进制字符串。
1.private static void GenerateAESKeys()
2. {
3. var sw = Stopwatch.StartNew();
4.for (int i = 0; i < NUM_AES_KEYS; i++)
5. {
6. var aesM = new AesManaged();
7. aesM.GenerateKey();
8.byte[] result = aesM.Key;
9. string hexStr = ConverToHexString(re
sult);
10. }
11. Console.WriteLine("AES:"+sw.Elapsed.ToS
tring());
12. }
13.
14.private static void ParallelGenerateAESKeys()
15. {
16. var sw = Stopwatch.StartNew();
17. System.Threading.Tasks.Parallel.For(1,
NUM_AES_KEYS + 1, (int i) =>
18. {
19. var aesM = new AesManaged();
20. aesM.GenerateKey();
21.byte[] result = aesM.Key;
22. string hexStr = ConverToHexString(r
esult);
23. });
24.
25. Console.WriteLine("Parallel_AES:" + sw.
Elapsed.ToString());
26. }
private static int NUM_AES_KEYS = 100000;
static void Main(string[] args)
{
Console.WriteLine("执行"+NUM_AES_KEYS+"次:"); GenerateAESKeys();
ParallelGenerateAESKeys();
Console.ReadKey();
}
执行1000000次
这里并行的时间是串行的一半。
五、Parallel.ForEach
在Parallel.For中,有时候对既有循环进行优化可能会是一个非常复杂的任务。Parallel.ForEach为固定数目的独立For Each循环迭代提供了负载均衡的并行执行,且支持自定义分区器,让使用者可以完全掌握数据分发。实质就是将所有要处理的数据区分为多个部分,然后并行运行这些串行循环。
修改上面的代码:
1.System.Threading.Tasks.Parallel.ForEach(Partitioner.
Create(1, NUM_AES_KEYS + 1), range =>
2. {
3. var aesM = new AesManaged();
4. Console.WriteLine("AES Range({0},{1}
循环开始时间:
{2})",range.Item1,range.Item2,DateTime.Now.TimeOfDay
);
5.
6.for (int i = range.Item1; i < range.
Item2; i++)
7. {
8. aesM.GenerateKey();
9.byte[] result = aesM.Key;
10. string hexStr = ConverToHexStri
ng(result);
11. }
12. Console.WriteLine("AES:"+sw.Elapsed
.ToString());
13. });
从执行结果可以看出,分了13个段执行的。
第二次执行还是13个段。速度上稍微有差异。开始没有指定分区数,Partitioner.Create使用的是内置默认值。
而且我们发现这些分区并不是同时执行的,大致是分了三个时间段执行。而且执行顺序是不同的。总的时间和Parallel.For的方法差不多。
public static ParallelLoopResult ForEach
Parallel.ForEach方法定义了source和Body两个参数。source是指分区器。提供了分解为多个分区的数据源。body是要调用的委托。它接受每一个已定义的分区作为参数。一共有20多个重载,在上面的例子中,分区的类型为
Tuple
Partitioner.Create 创建分区是根据逻辑内核数及其他因素决定。
1.public static OrderablePartitioner
Create(int fromInclusive, int toExclusive)
2. {
3.int num = 3;
4.if (toExclusive <= fromInclusive)
5.throw new ArgumentOutOfRangeException("toExc
lusive");
6.int rangeSize = (toExclusive - fromInclusive)
/ (PlatformHelper.ProcessorCount * num);
7.if (rangeSize == 0)
8. rangeSize = 1;
9.return Partitioner.Create
titioner.CreateRanges(fromInclusive, toExclusive, ra
ngeSize), EnumerablePartitionerOptions.NoBuffering);
10. }
因此我们可以修改分区数目,rangesize大致为250000左右。也就是说我的逻辑内核是4.
var rangesize = (int) (NUM_AES_KEYS/Environment.ProcessorCount) + 1;
System.Threading.Tasks.Parallel.ForEach(Partitioner.Create(1, NUM_AES_KEYS + 1,rangesize), range =>
再次执行:
分区变成了四个,时间上没有多大差别(第一个时间是串行时间)。我们看见这四个分区几乎是同时执行的。大部分情况下,TPL在幕后使用的负载均衡机制都是非常高效的,然而对分区的控制便于使用者对自己的工作负载进行分析,来改进整体的性能。
Parallel.ForEach也能对IEnumerable
1.private static void ParallelForEachGenerateMD5HasHes
()
2. {
3. var sw = Stopwatch.StartNew();
4. System.Threading.Tasks.Parallel.ForEach(
Enumerable.Range(1, NUM_AES_KEYS), number =>
5. {
6. var md5M = MD5.Create();
7.byte[] data = Encoding.Unicode.GetBy
tes(https://www.wendangku.net/doc/f914816912.html,erName + number);
8.byte[] result = https://www.wendangku.net/doc/f914816912.html,puteHash(dat
a);
9. string hexString = ConverToHexString
(result);
10. });
11. Console.WriteLine("MD5:"+sw.Elapsed.ToS
tring());
12. }
六、从循环中退出
和串行运行中的break不同,ParallelLoopState 提供了两个方法用于停止Parallel.For 和 Parallel.ForEach的执行。
Break:让循环在执行了当前迭代后尽快停止执行。比如执行到100了,那么循环会处理掉所有小于100的迭代。
Stop:让循环尽快停止执行。如果执行到了100的迭代,那不能保证处理完所有小于100的迭代。
修改上面的方法:执行3秒后退出。
1.private static void ParallelLoopResult(ParallelLoopR
esult loopResult)
2. {
3. string text;
4.if (loopResult.IsCompleted)
5. {
6. text = "循环完成";
7. }
8.else
9. {
10.if (loopResult.LowestBreakIteration
.HasValue)
11. {
12. text = "Break终止";
13. }
14.else
15. {
16. text = "Stop 终止";
17. }
18. }
19. Console.WriteLine(text);
20. }
21.
22.
23.private static void ParallelForEachGenerate
MD5HasHesBreak()
24. {
25. var sw = Stopwatch.StartNew();
26. var loopresult= System.Threading.Tasks.
Parallel.ForEach(Enumerable.Range(1, NUM_AES_KEYS), (int number,ParallelLoopState loopState) =>
27. {
28. var md5M = MD5.Create();
29.byte[] data = Encoding.Unicode.GetB
ytes(https://www.wendangku.net/doc/f914816912.html,erName + number);
30.byte[] result = https://www.wendangku.net/doc/f914816912.html,puteHash(da
ta);
31. string hexString = ConverToHexStrin
g(result);
32.if (sw.Elapsed.Seconds > 3)
33. {
34. loopState.Stop();
35. }
36. });
37. ParallelLoopResult(loopresult);
38. Console.WriteLine("MD5:" + sw.Elapsed);
39. }
七、捕捉并行循环中发生的异常。
当并行迭代中调用的委托抛出异常,这个异常没有在委托中被捕获到时,就会变成一组异常,新的System.AggregateException负责处理这一组异常。
1.private static void ParallelForEachGenerateMD5HasHes
Exception()
2. {
3. var sw = Stopwatch.StartNew();
4. var loopresult = new ParallelLoopResult(
);
5.try
6. {
7. loopresult = System.Threading.Tasks.
Parallel.ForEach(Enumerable.Range(1, NUM_AES_KEYS), (number, loopState) =>
8. {
9. var md5M = MD5.Create();
10.byte[] data = Encoding.Unicode.
GetBytes(https://www.wendangku.net/doc/f914816912.html,erName + number);
11.byte[] result = https://www.wendangku.net/doc/f914816912.html,puteHas
h(data);
12. string hexString = ConverToHexS
tring(result);
13.if (sw.Elapsed.Seconds > 3)
14. {
15.throw new TimeoutException(
"执行超过三秒");
16. }
17. });
18. }
19.catch (AggregateException ex)
20. {
21. foreach (var innerEx in ex.InnerEx
ceptions)
22. {
23. Console.WriteLine(innerEx.ToStr
ing());
24. }
25. }
26.
27. ParallelLoopResult(loopresult);
28. Console.WriteLine("MD5:" + sw.Elapsed);
29. }
结果:
异常出现了好几次。
八、指定并行度。
TPL的方法总会试图利用所有可用的逻辑内核来实现最好的结果,但有时候你并不希望在并行循环中使用所有的内核。比如你需要留出一个不参与并行计算的内核,来创建能够响应用户的应用程序,而且这个内核需要帮助你运行代码中的其他部分。这个时候一种好的解决方法就是指定最大并行度。
这需要创建一个ParallelOptions的实例,设置MaxDegreeOfParallelism的值。
1.private static void ParallelMaxDegree(int maxDegree)
2. {
3. var parallelOptions = new ParallelOption
s();
4. parallelOptions.MaxDegreeOfParallelism =
maxDegree;
5.
6. var sw = Stopwatch.StartNew();
7. System.Threading.Tasks.Parallel.For(1, N
UM_AES_KEYS + 1, parallelOptions, (int i) =>
8. {
9. var aesM = new AesManaged();
10. aesM.GenerateKey();
11.byte[] result = aesM.Key;
12. string hexStr = ConverToHexString(r
esult);
13. });
14. Console.WriteLine("AES:" + sw.Elapsed.T
oString());
15. }
调用:如果在四核微处理器上运行,那么将使用3个内核。
ParallelMaxDegree(Environment.ProcessorCount - 1);
时间上大致慢了点(第一次Parallel.For 3.18s),但可以腾出一个内核来处理其他的事情。
小结:这次学习了Parallel相关方法以及如何退出并行循环和捕获异常、设置并行度,还有并行相关的知识。园子里也有类似的博客。但作为自己知识的管理,在这里梳理一遍。
【编辑推荐】
1.关于面向切面编程的部分内容-错误处理机制
2.程序员编程时心力交瘁失去工作热情咋整?
3.5个可以教你编程的游戏
4.十一款值得深入了解的物联网编程语言
《C#语言程序设计》实验报告 学院:信息学院 专业:计算机科学与技术 指导教师: 报告人: 学号: 班级:
实验一简单编程练习 一、目的与要求 1、熟悉Visual https://www.wendangku.net/doc/f914816912.html,集成开发环境(IDE) 2、熟悉C#源程序语言的编辑、编译和运行过程 3、能够创建、编译和执行一个简单的C#程序 二、实验仪器 Windows操作系统,Microsoft Visual Studio .NET 2010。 三、实验内容 1.开发一个简单的控制台应用程序,该程序完成一段字符的输入,然后输出该字符串。 2.开发一个简单的Windows应用程序,该程序通过鼠标单击按钮在文本框中显示一行字符串。 四、实验过程及结果 1.控制台应用程序 (1)启动.NET 2010。 (2)在“文件”菜单上找到“新建”->“项目”,打开“新建项目”对话框。 (3)在模板一栏选择“控制台应用程序”,在名称一栏输入项目的名称。 (4)位置一栏内指定的是项目存放的目录,可以更改为自己定制的目录,也可以使用系统提供的默认路径。 (5)按确定以后用下面的代码替换Program.cs已有的所有代码: using System; namespace TestConsole { class Program { static void Main() { string str; System.Console.WriteLine("Please input a string !"); str = System.Console.ReadLine(); System.Console.WriteLine(str); } } } (6)运行应用程序(ctrl + F5)。
v1.0 可编辑可修改 院系:计算机科学学院 专业:计算机科学与技术 年级: 2013级 课程名称:软件工程 组员:司少武(1135) 兰少雄(1136) 张宇(1133) 纳洪泽(1132) 指导教师:刘卫平 2015年 12月 26 日
聊天室 1 前言 即时消息系统的研究现状 即时消息系统[1](Instant Messenger,IM)是一种在后 PC 时代兴起的,以Internet 网络为基础的,允许交互双方即时地传送文字、语音、视频等信息,能够跟踪网络用户在线状态的网络应用软件。即时消息系统产生有着深刻的社会原因:人们都有渴望社交,获得社会尊重、实现自我的需求,这正是即时消息软件风行的原动力,而物质文明的日益发达所带来副作用,又使得人们习惯与周围的人保持距离,以致人们更愿意对陌生人敞开心扉,在网络中可以跨越年龄、身份、行业、地域的限制,达到人与人、人与信息之间的零距离交流。从这点上讲,即时消息系统的出现改变了人们的沟通方式和交友文化,大大拓展了个人生活交流的空间。 本工程的主要内容 随着互联网逐步普及,人们的生活和工作也越来越离不开信息网络的支持,而聊天室是人们最常见,最直接的网上交流的方式。本聊天系统以聊天交流为主,为广大用户提供一个借助网络进行人际交往的平台,也是网络与现实最贴近的实用型网站。本文所介绍的网络聊天系统是基于开放的JAVA应用程序开发设计的,其主要特性是能动态、实时的完成信息的传递,且具有高效的交互性,更有效的处理客户请求,且具有脱离数据库技术方法,易于维护和更新的特点。 2 需求分析 本系统所要实现的主要功能是当用户聊天时,将当前用户名、聊天对象、聊天内容、聊天语气和是否私聊进行封装,然后与服务器建立Socket连接,再用对象输出流包装Socket的输出流将聊天信息对象发送给服务器端当用户发送聊天信息时,服务端将会收到客户端用Socket传输过来的聊天信息对象,然后将其强制转换为Chat对象,并将本次用户的聊天信息对象添加
第一章目的与要求 书上有。 第二章需求分析 软件设计的一个重要的环节就是需求分析。本章在对ATM取款机管理系统的应用情况作了全面调查的基础上,确定系统目标,并对系统所需要的基础功能进行分析,从而确定用户的需求。以下是ATM取款机管理系统所需要的需求分析。 ATM管理系统包括六个模块:登录,挂失功能,修改密码,取款功能,转账功能,查询功能。 ①登录: 输入银行卡的账号,密码,验证银行卡的卡号,密码是否正确,之后进入主界面 ②挂失功能: 确认是否对银行卡进行挂失,挂失后账户无法操作 ③修改密码: 用户可自由对其银行卡密码进行修改,修改之后的新密码将会覆盖其原密码 ④取款功能: 用户可自由取得所持银行卡内的存款,所取款数必须在其原有账户余额之内 ⑤转账功能: 用户可将本账户中的存款转入其他账户,转账过程中,需要转入账户的账
号 ⑥查询功能: 用户可查询用户信息,其中包括用户姓名、用户身份证号码、银行卡号以及存款余额 模拟ATM取款机的操作: 首先录入账户信息,格式如下 ㈠、登录功能:输入帐号和密码进行登录,如密码错误提示重新输入密码,如帐号错误提示无此帐户。 ㈡、查询功能:查询账户余额。 ㈢、取款功能:取款,更新余额。 ㈣、挂失功能:挂失后,该账户不能进行任何操作。 ㈤、修改密码:输入原密码和新设置密码。 ㈥、转账功能:输入转账的转出账户以及转账金额,进行转账。
第三章设计分析 3.1、ATM系统管理 3.1.1系统基本功能 首先,确定系统中存在两种用户,一种是ATM,可以进行信息录入和后台管理。另外一种是取款人,取款人主要是进行信息的查询,不能进行信息录入。所以在创建类的时候,先创建一个ATM类,然后创建取款人类,并与ATM 类形成有元,进而继承ATM的所有功能,并添加录入信息的功能。 基本功能: 登录功能---------输入帐号和密码进行登录,如密码错误提示重新输入密码,如帐号错误提示无此帐户。 查询功能---------查询账户余额。 取款功能---------取款,更新余额。 挂失功能---------挂失后,该账户不能进行任何操作。 修改密码---------输入原密码和新设置密码。 转账功能---------输入转账的转出账户以及转账金额,进行转账。、
第一章 1.填空题 (1).NET Framework主要包括公共语言运行库CLR和类库。 (2)https://www.wendangku.net/doc/f914816912.html,网站在编译时,首先将语言代码编译成微软中间语言MSIL。(3)一台IIS Web服务器IP地址为210.78.60.19,网站端口号为8000,则要访问虚拟目录xxxy中default.aspx的URL为 http://210.78.60.19.8000/xxxy/default.aspx。 (4)可以通过复制网站同步网站上的一个文件。 2.是非题 (1)托管代码是以CLR为基础的代码。(√) (2)https://www.wendangku.net/doc/f914816912.html,3.5仍使用https://www.wendangku.net/doc/f914816912.html,2.0引擎。(√) (3).NET Framework 3.0是.NET Framework 3.5的一部分。(√) (4)https://www.wendangku.net/doc/f914816912.html, 3.5是边解释边执行的。(×) (5)在Visual Studio 2008环境中开发网站必须安装IIS。(×) 3.选择题 (1).NET Framwork3.5不包括(A) A. .NET Framework 1.1 B. .NET Framework 2.0 C. LINQ D. https://www.wendangku.net/doc/f914816912.html, AJAX (2)下面(D)网站在建立时要求安装Microsoft FrontPage服务器扩展。 文件系统 B. 本地IIS C. FTP站点 D.远程站点 (3)发布网站后不可能存在的文件夹是(B) A.App_Data B.App_Code C.App_Themes D.bin 第二章 1.填空题 (1)Visual Studio 2008 默认建立的XHIML文件类型是Transitional。 (2)利用XHTML建立一个链接到jxst@https://www.wendangku.net/doc/f914816912.html,邮箱的元素是……。 (3)存放Web窗体页C#代码的模型有单文件页模型和代码隐藏页模型。(4)单文件页模型中,C#代码必须包含于之间。 (5)外部样式表通过元素链接到网页。 (6)XML主要用于传输和存储数据。 2.是非题 (1)XHTML是HTML的子集。(×) (2)XHTML中每个元素都有结束标记。(√) (3).htm文件不需要编译,直接从Web服务器下载到浏览器执即可。(√)(4)基于类的样式在定义是要加前缀“#”。(×) (5)JavaScript代码必须包含在