8天玩转并行开发——第一天 Parallel的使用

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

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

 

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

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

class Program
{
    static void Main(string[] args)
    {
        var watch = Stopwatch.StartNew();

        watch.Start();

        Run1();

        Run2();

        Console.WriteLine("我是串行开发,总共耗时:{0}\n", watch.ElapsedMilliseconds);

        watch.Restart();

        Parallel.Invoke(Run1, Run2);

        watch.Stop();

        Console.WriteLine("我是并行开发,总共耗时:{0}", watch.ElapsedMilliseconds);

        Console.Read();
    }

    static void Run1()
    {
        Console.WriteLine("我是任务一,我跑了3s");
        Thread.Sleep(3000);
    }

    static void Run2()
    {
        Console.WriteLine("我是任务二,我跑了5s");
        Thread.Sleep(5000);
    }
}

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

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

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

 

2:Parallel.for

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

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

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

class Program
    {
        static void Main(string[] args)
        {
            for (int j = 1; j < 4; j++)
            {
                Console.WriteLine("\n第{0}次比较", j);

                ConcurrentBag<int> bag = new ConcurrentBag<int>();

                var watch = Stopwatch.StartNew();

                watch.Start();

                for (int i = 0; i < 20000000; i++)
                {
                    bag.Add(i);
                }

                Console.WriteLine("串行计算:集合有:{0},总共耗时:{1}", bag.Count, watch.ElapsedMilliseconds);

                GC.Collect();

                bag = new ConcurrentBag<int>();

                watch = Stopwatch.StartNew();

                watch.Start();

                Parallel.For(0, 20000000, i =>
                {
                    bag.Add(i);
                });

                Console.WriteLine("并行计算:集合有:{0},总共耗时:{1}", bag.Count, watch.ElapsedMilliseconds);

                GC.Collect();

            }
        }
    }

 

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

 

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<int> bag = new ConcurrentBag<int>();

                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<int>();

                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的时候退出循环

class Program
    {
        static void Main(string[] args)
        {
            var watch = Stopwatch.StartNew();

            watch.Start();

            ConcurrentBag<int> bag = new ConcurrentBag<int>();

            Parallel.For(0, 20000000, (i, state) =>
                  {
                      if (bag.Count == 1000)
                      {
                          state.Break();
                          return;
                      }
                      bag.Add(i);
                  });

            Console.WriteLine("当前集合有{0}个元素。", bag.Count);

        }
    }

 

<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<int>();

            ParallelOptions options = new ParallelOptions();

            //指定使用的硬件线程数为1
            options.MaxDegreeOfParallelism = 1;

            Parallel.For(0, 300000, options, i =>
            {
                bag.Add(i);
            });

            Console.WriteLine("并行计算:集合有:{0}", bag.Count);

        }
    }
时间: 2024-10-21 13:29:13

8天玩转并行开发——第一天 Parallel的使用的相关文章

8天玩转并行开发——第六天 异步编程模型

原文:8天玩转并行开发--第六天 异步编程模型             在.net里面异步编程模型由来已久,相信大家也知道Begin/End异步模式和事件异步模式,在task出现以后,这些东西都可以被task包装 起来,可能有人会问,这样做有什么好处,下面一一道来.   一: Begin/End模式 1: 委托     在执行委托方法的时候,我们常常会看到一个Invoke,同时也有一对你或许不常使用的BeginInvoke,EndInvoke方法对,当然Invoke方法 是阻塞主线程,而Begi

8天玩转并行开发——第七天 简要分析任务与线程池

原文:8天玩转并行开发--第七天 简要分析任务与线程池          其实说到上一篇,我们要说的task的知识也说的差不多了,这一篇我们开始站在理论上了解下"线程池"和"任务"之间的关系,不管是 说线程还是任务,我们都不可避免的要讨论下线程池,然而在.net 4.0以后,线程池引擎考虑了未来的扩展性,已经充分利用多核微处理器 架构,只要在可能的情况下,我们应该尽量使用task,而不是线程池.   首先看一下task的结构 从图中我们可以看出Task.Factor

8天玩转并行开发——第三天 plinq的使用

原文:8天玩转并行开发--第三天 plinq的使用        相信在.net平台下,我们都玩过linq,是的,linq让我们的程序简洁优美,简直玩的是爱不释手,但是传统的linq只是串行代码,在并行的 年代如果linq不支持并行计算那该是多么遗憾的事情啊.    当然linq有很多种方式,比如linq to sql ,xml,object 等等,如果要将linq做成并行还是很简单的,这里我就举一个比较实际一点的例子, 我们知道为了更快的响应用户操作,码农们想尽了各种办法,绞尽了脑汁,其中有一

8天玩转并行开发——第八天 用VS性能向导解剖你的程序

原文:8天玩转并行开发--第八天 用VS性能向导解剖你的程序            最后一篇,我们来说说vs的"性能向导",通常我们调试程序的性能一般会使用Stopwatch,如果希望更加系统的了解程序,我们就需要 用到"性能向导",通过性能报告便于我们快速的发现并找到潜在的性能问题.   首先我们上一段需要改进的代码: 1 using System; 2 using System.Collections.Generic; 3 using System.Linq;

8天玩转并行开发——第二天 Task的使用

     在我们了解Task之前,如果我们要使用多核的功能可能就会自己来开线程,然而这种线程模型在.net 4.0之后被一种称为基于 "任务的编程模型"所冲击,因为task会比thread具有更小的性能开销,不过大家肯定会有疑惑,任务和线程到底有什么区别?   1:任务是架构在线程之上的,也就是说任务最终还是要抛给线程去执行. 2:任务跟线程不是一对一的关系,比如开10个任务并不是说会开10个线程,这一点任务有点类似线程池,但是任务相比线程池有很小       的开销和精确的控制.  

8天玩转并行开发——第四天 同步机制(上)

     在并行计算中,不可避免的会碰到多个任务共享变量,实例,集合.虽然task自带了两个方法:task.ContinueWith()和Task.Factory .ContinueWhenAll()来实现任务串行化,但是这些简单的方法远远不能满足我们实际的开发需要,从.net 4.0开始,类库给我们提供了很多 的类来帮助我们简化并行计算中复杂的数据同步问题.   大体上分为二种: ①   并发集合类:           这个在先前的文章中也用到了,他们的出现不再让我们过多的关注同步细节. ②

8天玩转并行开发——第五天 同步机制(下)

         承接上一篇,我们继续说下.net4.0中的同步机制,是的,当出现了并行计算的时候,轻量级别的同步机制应运而生,在信号量这一块 出现了一系列的轻量级,今天继续介绍下面的3个信号量 CountdownEvent,SemaphoreSlim,ManualResetEventSlim.   一:CountdownEvent      这种采用信号状态的同步基元非常适合在动态的fork,join的场景,它采用"信号计数"的方式,就比如这样,一个麻将桌只能容纳4个 人打麻将,如果

手把手玩转win8开发系列课程(8)

这节,我们继续上节的一个议程. ①添加一个主页 现在我已经定义了一个ViewModel,我就开始整合项目.第一步就是添加主页到添加到项目中去.我明白项目为何吧页面创建为空白页面,但这个空白的页面起名字叫空白一点儿意义都没有吧,我这里需要给新页面起一个更有意义的页面.原先的空白页面,我已经吧他干掉了. 鄙人是个贱体,我更喜欢添加一些新的文件夹,使其更加的有层次.因此,我加了一个新的文件夹加做pages.在此文件夹下,我右击我添加了一个叫做list的xaml的文件. 特别提醒,如果你是xaml的一个

基于DotNet构件技术的企业级敏捷软件开发平台 AgileEAS.NET - 敏捷并行开发方法

      首先声明,此并行开发非指并行计算,而是指实践软件工程所总结的一些心得和做法.       敏捷并行开发方法,是利用软件构件技术等技术,以及平台+插件化开发技术整合而出的一种快速开发模式:并行是指产品在生命周期内,项目管理过程.项目研发过程和机构支撑过程"并行"开展,项目研发过程中各个阶段有限度"并行"开展. 并行开发       在传统的瀑布开发模式下,同一软件的多个阶段是顺序地被开发出来的,也就是说第一阶段全部开发结束后才开始第二阶段的开发:同样,等第