zoukankan      html  css  js  c++  java
  • C#并行和多线程编程_(1)认识Parallel

    Parallel: 英 [ˈpærəlel]    美 [ˈpærəˌlɛl] ,并联的,并行的。

              随着多核时代的到来,并行开发越来越展示出它的强大威力!使用并行程序,充分的利用系统资源,提高程序的性能。在.net 4.0中,微软给我们提供了一个新的命名空间:System.Threading.Tasks。这里面有很多关于并行开发的东西,今天第一篇就介绍下最基础,最简单的——认识和使用Parallel。

    一、 Parallel的使用

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

    1、Parallel.Invoke:最简单,最简洁的将串行的代码并行化。

    先介绍一下StopWatch(using System.Diagnostics)

    其使用方法如下:

    var stopWatch = new StopWatch();   //创建一个Stopwatch实例

    stopWatch.Start();   //开始计时

    stopWatch.Stop();   //停止计时

    stopWatch.Reset();  //重置StopWatch

    stopWatch.Restart(); //重新启动被停止的StopWatch

    stopWatch.ElapsedMilliseconds //获取stopWatch从开始到现在的时间差,单位是毫秒

    新建一个控制台程序,添加一个类,代码如下:

    public class ParallelDemo
          {
             private Stopwatch stopWatch = new Stopwatch();
    
             public void Run1()
             {
                Thread.Sleep(2000);
                Console.WriteLine("Task 1 is cost 2 sec");
             }
             public void Run2()
             {
                Thread.Sleep(3000);
                Console.WriteLine("Task 2 is cost 3 sec");
             }
    
             public void ParallelInvokeMethod()
             {
                stopWatch.Start();
                Parallel.Invoke(Run1, Run2);
                stopWatch.Stop();
                Console.WriteLine("Parallel run " + stopWatch.ElapsedMilliseconds + " ms.");
                
                stopWatch.Restart();
                Run1();
                Run2();
                stopWatch.Stop();
                Console.WriteLine("Normal run " + stopWatch.ElapsedMilliseconds + " ms.");
             }
    }      

    代码很简单,首先新加一个类,在类中写了两个方法,Run1和Run2,分别等待一定时间,输出一条信息,然后写了一个测试方法ParallelInvokeMethod,分别用两种方法调用Run1和Run2,然后在main方法中调用,下面来看一下运行时间如何:

    这里可以看到:正常调用的话应该是5秒多,而Parallel.Invoke只用了3秒多,也就是耗时最少的那个方法,可以看出方法是并行执行的,执行效率提高了很多。

    2、Parallel.For

    这个方法和For循环的功能相似,下面就在类中添加一个方法来测试一下:

    public void ParallelForMethod()
         {
                stopWatch.Start();
                for (int i = 0; i < 10000; i++)
                {
                   for (int j = 0; j < 60000; j++)
                   {
                      int sum = 0;
                      sum += i;
                   }
                }
                stopWatch.Stop();
                Console.WriteLine("NormalFor run " + stopWatch.ElapsedMilliseconds + " ms.");
    
                stopWatch.Reset();
                stopWatch.Start();
                Parallel.For(0, 10000, item =>
                {
                   for (int j = 0; j < 60000; j++)
                   {
                      int sum = 0;
                      sum += item;
                   }
                });
                stopWatch.Stop();
                Console.WriteLine("ParallelFor run " + stopWatch.ElapsedMilliseconds + " ms.");
               
         }

    写了两个循环,做了一些没有意义的事情,目的主要是为了消耗CPU时间,同理在main方法中调用,运行结果如下图:

    可以看到,Parallel.For所用的时间比单纯的for快了1秒多,可见提升的性能是非常可观的。那么,是不是Parallel.For在任何时候都比for要快呢?答案当然是“不是”,要不然微软还留着for干嘛?

    下面修改一下代码,添加一个全局变量num,代码如下:

    public void ParallelForMethod()
             {
                var obj = new Object();
                long num = 0;
                ConcurrentBag<long> bag = new ConcurrentBag<long>();
    
                stopWatch.Start();
                for (int i = 0; i < 10000; i++)
                {
                   for (int j = 0; j < 60000; j++)
                   {
                      //int sum = 0;
                      //sum += item;
                      num++;
                   }
                }
                stopWatch.Stop();
                Console.WriteLine("NormalFor run " + stopWatch.ElapsedMilliseconds + " ms.");
    
                stopWatch.Reset();
                stopWatch.Start();
                Parallel.For(0, 10000, item =>
                {
                   for (int j = 0; j < 60000; j++)
                   {
                      //int sum = 0;
                      //sum += item;
                      lock (obj)
                      {
                         num++;
                      }
                   }
                });
                stopWatch.Stop();
                Console.WriteLine("ParallelFor run " + stopWatch.ElapsedMilliseconds + " ms.");
               
             }

    Parallel.For由于是并行运行的,所以会同时访问全局变量num,为了得到正确的结果,要使用lock,此时来看看运行结果:

    这里,Parallel.For竟然用了15秒多,而for跟之前的差不多。这主要是由于并行同时访问全局变量,会出现资源争夺,大多数时间消耗在了资源等待上面。

    一直说并行,那么从哪里可以看出来Parallel.For是并行执行的呢?下面来写个测试代码:

    Parallel.For(0, 100, i =>
                {
                   Console.Write(i + "	");
                });

    从0输出到99,运行后会发现输出的顺序不对,用for顺序肯定是对的,并行同时执行,所以会出现输出顺序不同的情况。

    3、Parallel.Foreach

     这个方法跟Foreach方法很相似。

                List<int> list = new List<int>();
                list.Add(0);
                Parallel.ForEach(list, item =>
                {
                    //DoWork(item);
                });

    二、 Parallel中途退出循环和异常处理

    1、当我们使用到Parallel,必然是处理一些比较耗时的操作,当然也很耗CPU和内存,如果我们中途向停止,怎么办呢?

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

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

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

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

    下面来写一段代码测试一下:

    public void ParallelBreak()
             {
                ConcurrentBag<int> bag = new ConcurrentBag<int>();
                stopWatch.Start();
                Parallel.For(0, 1000, (i, state) =>
                {
                   if (bag.Count == 300)
                   {
                      state.Stop();
                      return;
                   }
                   bag.Add(i);
                });
                stopWatch.Stop();
                Console.WriteLine("Bag count is " + bag.Count + ", " + stopWatch.ElapsedMilliseconds);
             }

    这里使用的是Stop,当数量达到300个时,会立刻停止;可以看到结果"Bag count is 300",如果用break,可能结果是300多个或者300个,大家可以测试一下。

    2、异常处理

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

    这里我们修改Parallel.Invoke的代码,修改后代码如下:

    public class ParallelDemo
          {
             private Stopwatch stopWatch = new Stopwatch();
    
             public void Run1()
             {
                Thread.Sleep(2000);
                Console.WriteLine("Task 1 is cost 2 sec");
                throw new Exception("Exception in task 1");
             }
             public void Run2()
             {
                Thread.Sleep(3000);
                Console.WriteLine("Task 2 is cost 3 sec");
                throw new Exception("Exception in task 2");
             }
    
             public void ParallelInvokeMethod()
             {
                stopWatch.Start();
                try
                {
                   Parallel.Invoke(Run1, Run2);
                }
                catch (AggregateException aex)
                {
                   foreach (var ex in aex.InnerExceptions)
                   {
                      Console.WriteLine(ex.Message);
                   }
                }
                stopWatch.Stop();
                Console.WriteLine("Parallel run " + stopWatch.ElapsedMilliseconds + " ms.");
    
                stopWatch.Reset();
                stopWatch.Start();
                try
                {
                   Run1();
                   Run2();
                }
                catch(Exception ex)
                {
                   Console.WriteLine(ex.Message);
                }
                stopWatch.Stop();
                Console.WriteLine("Normal run " + stopWatch.ElapsedMilliseconds + " ms.");
             }
    }

    顺序调用方法把异常处理写一起了,这样只能捕获Run1的异常信息,大家可以分开写。捕获AggregateException 异常后,用foreach循环遍历输出异常信息,可以看到两个异常信息都显示了。

  • 相关阅读:
    Time Zone 【模拟时区转换】(HDU暑假2018多校第一场)
    HDU 1281 棋盘游戏 【二分图最大匹配】
    Codeforces Round #527 (Div. 3) F. Tree with Maximum Cost 【DFS换根 || 树形dp】
    Codeforces Round #527 (Div. 3) D2. Great Vova Wall (Version 2) 【思维】
    Codeforces Round #527 (Div. 3) D1. Great Vova Wall (Version 1) 【思维】
    Codeforces Round #528 (Div. 2, based on Technocup 2019 Elimination Round 4) C. Connect Three 【模拟】
    Avito Cool Challenge 2018 E. Missing Numbers 【枚举】
    Avito Cool Challenge 2018 C. Colorful Bricks 【排列组合】
    005 如何分析问题框架
    004 如何定义和澄清问题
  • 原文地址:https://www.cnblogs.com/yaosuc/p/4570993.html
Copyright © 2011-2022 走看看