C#的变迁史06 - C# 4.0 之并行处理篇

  前面看完了Task对象,这里再看一下另一个息息相关的对象Parallel。

Parallel对象

  Parallel对象封装了能够利用多核并行执行的多线程操作,其内部使用Task来分装多线程的任务并试图将它们分配到不同的内核中并行执行。请注意“试图”这个词,Parallel对象相当具有智能性,当它判断任务集并没有从并行运行中受益,就会选择按顺序运行。这样的做法是因为并非所有的项目都适合使用并行开发,创建过多并行任务可能会损害程序的性能,降低运行效率。

  Parallel对象是静态类,它主要有3个静态方法:Invoke,For,ForEach。针对这3个方法,该对象也提供了多种不同的重载方法,使用起来相当的简单。先看一个简单的例子:

复制代码

static void Main(string[] args)
{Parallel.Invoke(()=>Console.WriteLine("1st task!"),()=>Console.WriteLine("2nd task!"));
}

复制代码

  这个例子中的两个任务就是并行执行的,所以结果可能是第一个先完成,也可能是第二个先输出结果。是不是超级简单?有没有使用一下Parallel对象的冲动?

  下面这个网上的例子验证了一下运行时间上并行计算的优越性:

复制代码

private const int count = 1000000000;
private static void M1()
{Console.WriteLine("M1 is busy now");for (int i = 0; i < count; i++);Console.WriteLine("M1 is Done");
}
private static void M2()
{Console.WriteLine("M2 is busy now");for (int i = 0; i < count; i++);Console.WriteLine("M2 is Done");
}
static void Main(string[] args)
{// 顺序执行DateTime start1 = DateTime.Now;M1();M2();Console.WriteLine(DateTime.Now - start1);// 并行执行DateTime start2 = DateTime.Now;Parallel.Invoke(M1, M2);Console.WriteLine(DateTime.Now - start2);
}

复制代码

  在不同的机器上,得到的结果可能不同,但是基本上所有的多核机器上得到的结果一定是并行执行的时候耗时比较短,例子比较简单,但是道理确实很直接。

  通常来说,对于一个程序,性能提升的关键是将可以并行执行的同步程序改成并行执行。这个上面的例子也反应了修改后的效果。此外,对于程序来说,循环是影响复杂度的最直接的因素,这个我们看看教科书上计算算法时间复杂度的算法就知道了,所以提升循环的执行效率往往是提升程序效率的关键一步。Parallel对象充分考虑到了这一点,提供了循环的并行版本。

例子一:For循环。

复制代码

static void Main(string[] args)
{for (int i = 0; i < 10; i++) Console.Write("{0} ", i);Console.WriteLine("by serial");Parallel.For(0, 10, (n) => Console.Write("{0} ", n));Console.WriteLine("by parallel");
}

复制代码

  从输出的结果你可以很容易发现后面的结果顺序完全是不固定的,这是并行的特征。

例子二:ForEach循环 

复制代码

static void Main(string[] args)
{int [] a = {1,2,3,4,5,6,7,8,9};foreach (var n in a) Console.Write("{0} ",n);Console.WriteLine("by serial");Parallel.ForEach(a, (n) => Console.Write("{0} ", n));Console.WriteLine("by parallel");
}

复制代码

  结果也很明显,就不多说了。

  通过上面的两个例子,其实我们就能发现一些问题:

1. 顺序要求严格的操作不能使用Parallel对象的方法,这个原因很简单。

2. 并不是所有的for语句都可以用并行处理来实行,只有在循环开始前循环的次数已确定的情况下可以采用并行处理。同理,do语句和while语句也不能采用并行处理。因为所谓“并行”就是在判定为“循环结束”之前,首先要把将要执行的循环实现分配好。

  好了,既然是对循环的并行处理,那就避不开break与continue的问题,也就是循环的主动中止问题。

循环的主动中止

  在Parallel对象中,也可以主动中止循环的执行:调用ParallelLoopState实例的Stop方法和Break方法,可以停止和中断当前循环的执行。其中,

1. Break 告知 Parallel 循环应在系统方便的时候尽早停止执行当前迭代之外的迭代,当前迭代之前的迭代任然会完成。
2. Stop 告知 Parallel 循环应在系统方便的时候尽早停止执行,不管其他的线程执行到什么程度。
  通常使用Stop会立即停止循环,使用Break却会执行完毕当前迭代次序前面的迭代后停止循环。例如,对于从 0 到 1000 并行迭代的 for 循环,如果从第 100 此迭代开始调用 Break,则低于 100 的所有迭代仍会运行,从 101 到 1000 的迭代则不一定会执行,注意是“不一定”,因为是并行执行的,说不定某些次序在后面的迭代已经执行了。看一下例子:

复制代码

static void Main(string[] args)
{DemoStop();DemoBreak();
}/// <summary>
/// 中断Stop
/// </summary>
static void DemoStop()
{List<int> data = new List<int>(){ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};Parallel.For(0, data.Count, (i, LoopState) =>{if (i > 5)LoopState.Stop();Thread.Sleep(500);Console.WriteLine(i);});Console.WriteLine("Stop执行结束。");
}
/// <summary>
/// 中断Break
/// </summary>
static void DemoBreak()
{List<int> data = new List<int>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};Parallel.ForEach(data, (i, LoopState) =>{if (i > 5)LoopState.Break();Thread.Sleep(500);Console.WriteLine(i);});Console.WriteLine("Break执行结束。");
}

复制代码

  运行一下,对比结果,细细体会一下输出的结果,我想你就会清楚Stop方法与Break方法的区别。

  当然了,前面讲的使用CancellationTokenSource取消线程的方式这里任然是适用的,不过需要通过ParallelOptions传给Parallel对象对应的重载方法。ParallelOptions对象还可以配置其他的一些参数,比如最大的并行数量(其实就是使用的最大内核数量)等等。看一个简单的例子:

复制代码

CancellationTokenSource token = new CancellationTokenSource();
Task.Factory.StartNew(() =>
{Thread.Sleep(5000);token.Cancel();Console.WriteLine("Token Cancelled.");
});ParallelOptions loopOptions = new ParallelOptions()
{CancellationToken = token.Token,MaxDegreeOfParallelism = 2
};try
{Parallel.For(0, Int64.MaxValue, loopOptions, i =>{Console.WriteLine("i={0},thread id={1}", i, Thread.CurrentThread.ManagedThreadId);Thread.Sleep(1000);});
}
catch (OperationCanceledException)
{Console.WriteLine("Exception...");
}

复制代码

  讨论完了各种正常情况,下面来看一下不正常的情况:异常问题。

异常问题

  和普通的for/foreach中发生异常的表现一样,Parallel循环中的任何异常都会使整个循环终止,不过由于整个循环是分核同时进行的,因此整个循环不会立即终止,这个很好理解。循环中停止前所有的异常都会被封装在AggregateException的InnerExceptions中。捕获这些异常的方式很简单,使用try/catch就可以了,看一下下面的代码:

复制代码

try
{Parallel.For(0, 5, (i) =>{throw new Exception(i.ToString());});
}
catch (AggregateException ae)
{foreach (var exp in ae.InnerExceptions){Console.WriteLine(exp.Message);}
}

复制代码

  这段代码将会输出0-4的子集(也有可能是0-4全部输出,因为5个线程都很快)。

  不过,与Parallel.For和ForEach不一样的是,Parallel.Invoke总是会把所有任务都执行完,然后把所有的异常包装在AggregateException中。其实道理与上面的循环是一样的,都是把应该执行的任务执行完,来看这段代码:

复制代码

try
{Parallel.Invoke(() => { throw new Exception("1"); },() => { Thread.Sleep(1500); throw new Exception("2"); },() => { Thread.Sleep(3000); throw new Exception("3"); });
}
catch (AggregateException ae)
{foreach (var ex in ae.InnerExceptions){Console.WriteLine(ex.Message);}
}

复制代码

  结果会输出:3 2 1。

  除此以外,Task.WaitAll和Parallel.Invoke是类似,任何一个(或多个)Task的异常不会影响任何其他Task的执行。

复制代码

try
{var t1 = Task.Factory.StartNew(() =>{Thread.Sleep(500);throw new Exception("1");});var t2 = Task.Factory.StartNew(() =>{Thread.Sleep(1000);throw new Exception("2");});Task.WaitAll(t1, t2);
}
catch (AggregateException ae)
{foreach (var exp in ae.InnerExceptions){Console.WriteLine(exp.Message);}
}

复制代码

  这段代码会输出:1 2。

  两个异常都会在AggregateException中的InnerExceptions属性中。不过很显然异常的顺序与上一个例子有点不同,这个需要注意一点。

  其实,在新的.NET类库中,不仅通过增加Parallel对象来增强并行处理的能力,而且在Linq语句中也有相应的增强,那就是PLinq。

PLinq简介

  PLINQ也就是Parallel Linq,它的使用方法是非常简单。
  下例本身没有什么太大意义,只不过是找出“2”,然后输出:

复制代码

using System;
using System.Linq;
using System.Threading.Tasks;
class Program
{static void Main(string[] args){int[] ar = { 1, 2, 3 };var q1 = from n in arwhere n == 2select n;foreach (var n in q1){Console.WriteLine("found {0}", n);}}
}

复制代码

如果把上例改成用并行处理,只要在查询表达式中追加AsParallel方法就可以了:

var q1 = from n in ar.AsParallel()where n == 2select n;

函数形式也是一样的。例如下面这个查询表达式:

var q1 = ar.Where((c) => c == 2);

改成并行执行也就是插入AsParallel方法就可以了:

var q1 = ar.AsParallel().Where((c) => c == 2);

  使用PLINQ是如此的简单,只要用一个方法就可以用并行来处理查询表达式了。但是,正如前面所讲的并行计算并不是适用于任何场合的灵丹妙药,它也有不太适用的场合:
1. 在大量使用查询表达式的时候,并不是每一句查询表达式都是性能瓶颈的关键,如果每一个查询表达式都插入AsParallel方法,不会带来太大好处,在浪费时间的同时,代码的可读性也降低了。
2. 插入AsParallel方法后,结果会发生变化,这个自然很好理解,因为并行执行了嘛,顺序得不到保证,所以与顺序有关的操作是适合使用同步操作的,并行执行就可能导致问题。


  其实AsParallel方法只是PLinq的基本入口点,在System.Linq.ParallelEnumerable类中,包含了并行查询的大部分其他有用的方法,比如:AsSequential(指定查询的其余部分应像非并行 LINQ 查询一样按顺序运行),AsOrdered(指定 PLINQ 应保留查询的其余部分的源序列排序,直到例如通过使用 orderby子句更改排序为止),AsUnordered(指定查询的其余部分的 PLINQ 不需要保留源序列的排序)等等方法。这个查看一下MSDN就可以了,使用起来还是比较方便的。也可查看博客园中的一些详细的文章,比如:http://www.cnblogs.com/leslies2/archive/2012/02/07/2320914.html。

 

  并行计算就简单总结这些了,铭记一点:并行执行的任务要保证是顺序无关的,独立的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/438132.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

REVERSE-PRACTICE-BUUCTF-31

REVERSE-PRACTICE-BUUCTF-31[羊城杯 2020]login[羊城杯 2020]Bytecode[羊城杯 2020]babyre[ACTF新生赛2020]fungame[羊城杯 2020]login exe程序&#xff0c;运行后输入&#xff0c;无壳&#xff0c;ida分析 没找到主要逻辑&#xff0c;在字符串窗口看到一些“py”的字样&#…

C#的变迁史07 - C# 4.0 之线程安全集合篇

作为多线程和并行计算不得不考虑的问题就是临界资源的访问问题&#xff0c;解决临界资源的访问通常是加锁或者是使用信号量&#xff0c;这个大家应该很熟悉了。 而集合作为一种重要的临界资源&#xff0c;通用性更广&#xff0c;为了让大家更安全的使用它们&#xff0c;微软为我…

PWN-PRACTICE-BUUCTF-1

PWN-PRACTICE-BUUCTF-1test_your_ncripwarmup_csaw_2016ciscn_2019_n_1test_your_nc 附件的main函数直接system("/bin/sh")&#xff0c;nc直接连即可cat flag rip main函数中&#xff0c;gets函数读取一行会造成栈溢出 构造payload覆盖rip&#xff0c;使得return…

C#的变迁史08 - C# 5.0 之并行编程总结篇

C# 5.0 搭载于.NET 4.5和VS2012之上。 同步操作既简单又方便&#xff0c;我们平时都用它。但是对于某些情况&#xff0c;使用同步代码会严重影响程序的可响应性&#xff0c;通常来说就是影响程序性能。这些情况下&#xff0c;我们通常是采用异步编程来完成功能&#xff0c;这在…

REVERSE-PRACTICE-CTFSHOW-1

REVERSE-PRACTICE-CTFSHOW-1逆向签到题re2逆向4逆向5逆向签到题 ida打开即可得到明文flag re2 附件是一个加密过的flag文本和勒索病毒exe 运行程序&#xff0c;输入1&#xff0c;回车&#xff0c;直接退出&#xff0c;ida分析 选项1的逻辑为&#xff0c;打开flag.txt和enfl…

C#的变迁史09 - C# 5.0 之调用信息增强篇

Caller Information CallerInformation是一个简单的新特性&#xff0c;包括三个新引入的Attribute&#xff0c;使用它们可以用来获取方法调用者的信息&#xff0c; 这三个Attribute在System.Runtime.CompilerServices命名空间下&#xff0c;分别叫做CallerMemberNameAttribute&…

REVERSE-PRACTICE-CTFSHOW-2

REVERSE-PRACTICE-CTFSHOW-2re3红包题 武穆遗书数学不及格flag白给re3 main函数&#xff0c;分析可知&#xff0c;将输入的字符串按十六进制转成数字&#xff0c;写到v5&#xff0c;赋给v17[6] 当i等于6时&#xff0c;v16会加上输入的值&#xff0c;然后进入循环&#xff0c;最…

C#的变迁史10 - C# 5.0 之其他增强篇

1. 内置zip压缩与解压   Zip是最为常用的文件压缩格式之一&#xff0c;也被几乎所有操作系统支持。在之前&#xff0c;使用程序去进行zip压缩和解压要靠第三方组件去支持&#xff0c;这一点在.NET4.5中已有所改观&#xff0c;Zip压缩和解压功能已经内置于框架本身。这个功能使…

REVERSE-PRACTICE-CTFSHOW-3

REVERSE-PRACTICE-CTFSHOW-3签退神光签到baby_gay签退 .pyc文件&#xff0c;uncompyle6反编译&#xff0c;得到python源码&#xff0c;分析写在源码注释中 先变表base64&#xff0c;再凯撒加密&#xff0c;向后移动2位 import string c_charset string.ascii_uppercase str…

REVERSE-PRACTICE-CTFSHOW-4

REVERSE-PRACTICE-CTFSHOW-4encodeEasyBJD hamburger competitionJustREencode elf文件&#xff0c;upx脱壳&#xff0c;ida分析 交叉引用字符串"Please input your flag:"&#xff0c;来到sub_804887C函数 输入经过三次变换&#xff0c;先是变表base64&#xff0c;…

CSS 基础框盒模型介绍

当对一个文档进行布局&#xff08;lay out&#xff09;的时候&#xff0c;浏览器的渲染引擎会根据标准之一的 CSS 基础框盒模型&#xff08;CSS basic box model&#xff09;&#xff0c;将所有元素表示为一个个矩形的盒子&#xff08;box&#xff09;。CSS 决定这些盒子的大小…

REVERSE-PRACTICE-CTFSHOW-5

REVERSE-PRACTICE-CTFSHOW-5re2_归心Mud[吃鸡杯]ezmore[吃鸡杯]有手就行re2_归心 exe程序&#xff0c;运行后要求输入flag&#xff0c;ida分析 函数窗没找到主逻辑函数&#xff0c;shiftF12看字符串窗口 发现有java/lang/String&#xff0c;com/exe4j/runtime/WinLauncher等字…

PWN-PRACTICE-BUUCTF-2

PWN-PRACTICE-BUUCTF-2pwn1_sctf_2016jarvisoj_level0ciscn_2019_c_1[第五空间2019 决赛]PWN5pwn1_sctf_2016 main函数中执行vuln函数 fgets限制了输入的长度&#xff0c;不足以构成栈溢出 通过将输入中的字符"I"替换成"you"&#xff0c;增加长度&#xf…

PWN-PRACTICE-BUUCTF-3

PWN-PRACTICE-BUUCTF-3[OGeek2019]babyropciscn_2019_n_8get_started_3dsctf_2016jarvisoj_level2[OGeek2019]babyrop 简单的ret2libc&#xff0c;构造rop main函数中读取一个随机数到buf中&#xff0c;传入sub_804871F 用"\x00"来绕过strlen和strncmp&#xff0c;b…

c#中常用集合类和集合接口之接口系列【转】

常用集合接口系列&#xff1a;http://www.cnblogs.com/fengxiaojiu/p/7997704.html 常用集合类系列&#xff1a;http://www.cnblogs.com/fengxiaojiu/p/7997541.html 大多数集合都在System.Collections&#xff0c;System.Collections.Generic两个命名空间。其中System.Colle…

PWN-PRACTICE-BUUCTF-4

PWN-PRACTICE-BUUCTF-4ciscn_2019_en_2bjdctf_2020_babystacknot_the_same_3dsctf_2016[HarekazeCTF2019]baby_ropciscn_2019_en_2 这题和ciscn_2019_c_1一模一样 栈溢出ret2libc&#xff0c;encrypt函数里的异或运算不用管 from pwn import * context.log_level"debug&…

PWN-PRACTICE-BUUCTF-5

PWN-PRACTICE-BUUCTF-5jarvisoj_level2_x64ciscn_2019_n_5others_shellcodeciscn_2019_ne_5jarvisoj_level2_x64 这题和[HarekazeCTF2019]baby_rop几乎一模一样 from pwn import * #context.log_level"debug" ioremote(node4.buuoj.cn,27023) elfELF(./level2_x64)…

Scrum敏捷开发沉思录

计算机科学的诞生&#xff0c;是世人为了用数字手段解决实际生活中的问题。随着时代的发展&#xff0c;技术的进步&#xff0c;人们对于现实世界中的问题理解越来越深刻&#xff0c;描述也越来越抽象&#xff0c;于是对计算机软件的需求也越来越高&#xff0c;越来越复杂&#…

PWN-PRACTICE-BUUCTF-6

PWN-PRACTICE-BUUCTF-6铁人三项(第五赛区)_2018_ropbjdctf_2020_babyropbabyheap_0ctf_2017pwn2_sctf_2016铁人三项(第五赛区)_2018_rop vulnerable_function函数中read构成栈溢出&#xff0c;ret2libc from pwn import * context.log_level"debug" ioremote(node4…

PWN-PRACTICE-BUUCTF-7

PWN-PRACTICE-BUUCTF-7jarvisoj_fmciscn_2019_s_3SROP解法ret2csu解法bjdctf_2020_babystack2[HarekazeCTF2019]baby_rop2jarvisoj_fm 格式化字符串漏洞&#xff0c;可以测出我们的输入在栈上的偏移为11 自己构造或者使用fmtstr_payload构造payload均可&#xff0c;目标是让x4…