博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
8天玩转并行开发——第一天 Parallel的使用
阅读量:5946 次
发布时间:2019-06-19

本文共 6447 字,大约阅读时间需要 21 分钟。

原文:

 

      随着多核时代的到来,并行开发越来越展示出它的强大威力,像我们这样的码农再也不用过多的关注底层线程的实现和手工控制,

要了解并行开发,需要先了解下两个概念:“硬件线程”和“软件线程”。

 

1. 硬件线程

    相信大家手头的电脑都是双核以上的,像我这样古董的电脑都是双核的,这样的双核叫做物理内核。

 

硬件线程又叫做逻辑内核,我们可以在”任务管理器“中查看”性能“标签页,如下图,我们知道有2个硬件线程。

 

 

一般情况下,一个物理内核对应一个逻辑内核,比如我这里的2对2。当然如果你的cpu采用的是超线程技术,那么可能就会有4个物理内核对应

8个硬件线程,现在有很多服务器都有8个硬件线程,上午在公司的服务器上截了个图。

我们要知道并行开发要做的事情就是将任务分摊给这些硬件线程去并行执行来达到负载和加速。

 

2. 软件线程

    相信这个大家最熟悉了,我们知道传统的代码都是串行的,就一个主线程,当我们为了实现加速而开了很多工作线程,这些工作线程

也就是软件线程。

 

好,我们知道了基本概念就ok了,在.net 4.0中,微软给我们提供了一个新的命名空间:System.Threading.Tasks。这里面有很多好玩

的东西,作为第一篇就介绍下最基础,最简单的Parallel的使用。

 

 

一: Parallel的使用

在Parallel下面有三个常用的方法invoke,for和forEach。

1:  Parallel.Invoke

    这是最简单,最简洁的将串行的代码并行化。

1 class Program  2 {
3 static void Main(string[] args) 4 {
5 var watch = Stopwatch.StartNew(); 6 7 watch.Start(); 8 9 Run1(); 10 11 Run2(); 12 13 Console.WriteLine("我是串行开发,总共耗时:{0}\n", watch.ElapsedMilliseconds); 14 15 watch.Restart(); 16 17 Parallel.Invoke(Run1, Run2); 18 19 watch.Stop(); 20 21 Console.WriteLine("我是并行开发,总共耗时:{0}", watch.ElapsedMilliseconds); 22 23 Console.Read(); 24 } 25 26 static void Run1() 27 {
28 Console.WriteLine("我是任务一,我跑了3s"); 29 Thread.Sleep(3000); 30 } 31 32 static void Run2() 33 {
34 Console.WriteLine("我是任务二,我跑了5s"); 35 Thread.Sleep(5000); 36 } 37 }

在这个例子中可以获取二点信息:

第一:一个任务是可以分解成多个任务,采用分而治之的思想。

第二:尽可能的避免子任务之间的依赖性,因为子任务是并行执行,所以就没有谁一定在前,谁一定在后的规定了。

 

2:Parallel.for

 我们知道串行代码中也有一个for,但是那个for并没有用到多核,而Paraller.for它会在底层根据硬件线程的运行状况来充分的使用所有的可

利用的硬件线程,注意这里的Parallel.for的步行是1。

这里我们来演示一下,向一个线程安全的集合插入数据,当然这个集合采用原子性来实现线程同步,比那些重量级的锁机制更加的节省消耗。

1  class Program  2     {
3 static void Main(string[] args) 4 {
5 for (int j = 1; j < 4; j++) 6 {
7 Console.WriteLine("\n第{0}次比较", j); 8 9 ConcurrentBag
bag = new ConcurrentBag
(); 10 11 var watch = Stopwatch.StartNew(); 12 13 watch.Start(); 14 15 for (int i = 0; i < 20000000; i++) 16 {
17 bag.Add(i); 18 } 19 20 Console.WriteLine("串行计算:集合有:{0},总共耗时:{1}", bag.Count, watch.ElapsedMilliseconds); 21 22 GC.Collect(); 23 24 bag = new ConcurrentBag
(); 25 26 watch = Stopwatch.StartNew(); 27 28 watch.Start(); 29 30 Parallel.For(0, 20000000, i => 31 {
32 bag.Add(i); 33 }); 34 35 Console.WriteLine("并行计算:集合有:{0},总共耗时:{1}", bag.Count, watch.ElapsedMilliseconds); 36 37 GC.Collect(); 38 39 } 40 } 41 }

 

可以看的出,加速的效果还是比较明显的。

 

3:Parallel.forEach

    forEach的独到之处就是可以将数据进行分区,每一个小区内实现串行计算,分区采用Partitioner.Create实现。

class Program     {
static void Main(string[] args) {
for (int j = 1; j < 4; j++) {
Console.WriteLine("\n第{0}次比较", j); ConcurrentBag
bag = new ConcurrentBag
(); var watch = Stopwatch.StartNew(); watch.Start(); for (int i = 0; i < 3000000; i++) {
bag.Add(i); } Console.WriteLine("串行计算:集合有:{0},总共耗时:{1}", bag.Count, watch.ElapsedMilliseconds); GC.Collect(); bag = new ConcurrentBag
(); watch = Stopwatch.StartNew(); watch.Start(); Parallel.ForEach(Partitioner.Create(0, 3000000), i => {
for (int m = i.Item1; m < i.Item2; m++) {
bag.Add(m); } }); Console.WriteLine("并行计算:集合有:{0},总共耗时:{1}", bag.Count, watch.ElapsedMilliseconds); GC.Collect(); } } }

这里还是要说一下:Partitioner.Create(0, 3000000)。

第一:我们要分区的范围是0-3000000。

第二:我们肯定想知道系统给我们分了几个区? 很遗憾,这是系统内部协调的,无权告诉我们,当然系统也不反对我们自己指定分区个数,

        这里可以使用Partitioner.Create的第六个重载,比如这样:Partitioner.Create(0, 3000000, Environment.ProcessorCount),

        因为 Environment.ProcessorCount能够获取到当前的硬件线程数,所以这里也就开了2个区。

 

下面分享下并行计算中我们可能有的疑惑?

<1> 如何中途退出并行循环?

      是的,在串行代码中我们break一下就搞定了,但是并行就不是这么简单了,不过没关系,在并行循环的委托参数中提供了一个

ParallelLoopState,该实例提供了Break和Stop方法来帮我们实现。

Break: 当然这个是通知并行计算尽快的退出循环,比如并行计算正在迭代100,那么break后程序还会迭代所有小于100的。

Stop:这个就不一样了,比如正在迭代100突然遇到stop,那它啥也不管了,直接退出。

 

下面举个例子,当迭代到1000的时候退出循环

1   class Program  2     {
3 static void Main(string[] args) 4 {
5 var watch = Stopwatch.StartNew(); 6 7 watch.Start(); 8 9 ConcurrentBag
bag = new ConcurrentBag
(); 10 11 Parallel.For(0, 20000000, (i, state) => 12 {
13 if (bag.Count == 1000) 14 {
15 state.Break(); 16 return; 17 } 18 bag.Add(i); 19 }); 20 21 Console.WriteLine("当前集合有{0}个元素。", bag.Count); 22 23 } 24 }

 

<2> 并行计算中抛出异常怎么处理?

 首先任务是并行计算的,处理过程中可能会产生n多的异常,那么如何来获取到这些异常呢?普通的Exception并不能获取到异常,然而为并行诞生的AggregateExcepation就可以获取到一组异常。

class Program {
static void Main(string[] args) {
try {
Parallel.Invoke(Run1, Run2); } catch (AggregateException ex) {
foreach (var single in ex.InnerExceptions) {
Console.WriteLine(single.Message); } } Console.Read(); } static void Run1() {
Thread.Sleep(3000); throw new Exception("我是任务1抛出的异常"); } static void Run2() {
Thread.Sleep(5000); throw new Exception("我是任务2抛出的异常"); } }

 

<3> 并行计算中我可以留一个硬件线程出来吗?

  默认的情况下,底层机制会尽可能多的使用硬件线程,然而我们使用手动指定的好处是我们可以在2,4,8个硬件线程的情况下来进行测量加速比。

class Program     {
static void Main(string[] args) {
var bag = new ConcurrentBag
(); ParallelOptions options = new ParallelOptions(); //指定使用的硬件线程数为1 options.MaxDegreeOfParallelism = 1; Parallel.For(0, 300000, options, i => {
bag.Add(i); }); Console.WriteLine("并行计算:集合有:{0}", bag.Count); } }

 

转载地址:http://hefxx.baihongyu.com/

你可能感兴趣的文章
Latex格式html文件转换pdf和docx文档
查看>>
【关于Number】JavaScript中关于Number的操作
查看>>
非泄露,NSA官方开源反汇编工具GHIDRA
查看>>
保持分布式团队同步
查看>>
Node.js v7 Beta版引入citgm
查看>>
微服务没有银弹 | Weibo Mesh 的工程化实践解读
查看>>
让你的系统“坚挺不倒”的最后一个大招——「降级」
查看>>
Git 2.5增加了工作树、改进了三角工作流、性能等诸多方面
查看>>
搭载AI引擎,腾讯云云镜开启全面防护模式
查看>>
不仅有Ubuntu,这家公司的Ubuntu Core预计使用翻倍
查看>>
JMS机制
查看>>
Grumpy:Google 用 Go 开发的 Python 运行时
查看>>
Kubernetes 1.14 版本发布:正式支持Windows 节点,持久化本地卷进入GA
查看>>
区块链和数据科学:如果同时应用这两种技术,将会实现什么?
查看>>
AVG插件泄漏Chrome用户数据
查看>>
免费微信公众号专用h5在线电影票API
查看>>
专访刘刚:360手机卫士的性能监控与优化
查看>>
FB正在大规模重构React Native,预计今年发布
查看>>
从0到1:腾讯Yoo视频底层页推荐系统实践
查看>>
推荐10个CI/CD工具,用于云平台集成交付
查看>>