5天玩转C#并行和多线程编程 —— 第一天 认识Parallel

5天玩转C#并行和多线程编程 —— 第一天 认识Parallel

 
原文: http://anneke.cn/ArticleInfo/Detial/23

目录

5天玩转C#并行和多线程编程 —— 第一天 认识Parallel

5天玩转C#并行和多线程编程 —— 第二天 并行集合和PLinq

5天玩转C#并行和多线程编程 —— 第三天 认识和使用Task

5天玩转C#并行和多线程编程 —— 第四天 Task进阶

5天玩转C#并行和多线程编程 —— 第五天 多线程编程大总结

5天所有的Demo:Demo


随着多核时代的到来,并行开发越来越展示出它的强大威力!使用并行程序,充分的利用系统资源,提高程序的性能。

在.net 4.0中,微软给我们提供了一个新的命名空间:System.Threading.Tasks。这里面有很多关于并行开发的东西,今天第一篇就介绍下最基础,最简单的——认识和使用Parallel类。

一、 Parallel类(提供对并行循环和区域的支持)的使用

在Parallel类下有三个常用的方法Invoke,For,ForEach

1. Parallel.Invoke:尽可能并行执行提供的每个操作(Executes each of the provided actions, possibly in parallel)

微软官方对该方法的作用表达很明确了,就是尽可能的同时执行你提供的方法

下面来看一个例子:新建一个控制台程序

  1. static void Main(string[] args)
  2. {
  3. #region Demo1
  4.  
  5. Stopwatch stopwatch = new Stopwatch();
  6. Console.WriteLine("Normal:");
  7. stopwatch.Start();
  8. RunOne();
  9. RunTwo();
  10. stopwatch.Stop();
  11. Console.WriteLine("Normal cost " + stopwatch.ElapsedMilliseconds + " milliseconds");
  12.  
  13. Console.WriteLine("----------------------------");
  14.  
  15. Console.WriteLine("Parallel:");
  16. stopwatch.Restart();
  17. Parallel.Invoke(RunOne, RunTwo);
  18. stopwatch.Stop();
  19. Console.WriteLine("Parallel cost " + stopwatch.ElapsedMilliseconds + " milliseconds");
  20.  
  21. #endregion
  22. Console.ReadKey();
  23. }
  24. static void RunOne()
  25. {
  26. Thread.Sleep(2000);
  27. Console.WriteLine("The RunOne cost 2 seconds.");
  28. }
  29. static void RunTwo()
  30. {
  31. Thread.Sleep(3000);
  32. Console.WriteLine("The RunTwo cost 3 seconds.");
  33. }
代码很简单,分别写了RunOne和RunTwo方法,等待一定的时间输出一句话,然后再main方法中用Stopwatch这个类来记录运行的总毫秒数,来比较串行和并行的运行效率

结果如下:

  1. Normal:
  2. The RunOne cost 2 seconds.
  3. The RunTwo cost 3 seconds.
  4. Normal cost 5001 milliseconds
  5. ----------------------------
  6. Parallel:
  7. The RunOne cost 2 seconds.
  8. The RunTwo cost 3 seconds.
  9. Parallel cost 3010 milliseconds

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

2. Parallel.For:执行 for(在 Visual Basic 中为 For)循环,其中可能会并行运行迭代(Executes a for (For in Visual Basic) loop in which iterations may run in parallel.)

这个方法和For循环功能相似,来写个例子看一下吧,还是控制台程序

  1. Stopwatch stopwatch=new Stopwatch();
  2. Console.WriteLine("Normal:");
  3. stopwatch.Start();
  4. for (int i = 0; i < 10000; i++)
  5. {
  6. for (int j = 0; j < 60000; j++)
  7. {
  8. int sum = 0;
  9. sum += i;
  10. }
  11. }
  12. stopwatch.Stop();
  13. Console.WriteLine("Normal cost " + stopwatch.ElapsedMilliseconds + " milliseconds");
  14. Console.WriteLine("----------------------------");
  15.  
  16. Console.WriteLine("Parallel:");
  17. stopwatch.Restart();
  18. Parallel.For(0, 10000, i =>
  19. {
  20. for (int j = 0; j < 60000; j++)
  21. {
  22. int sum = 0;
  23. sum += i;
  24. }
  25. });
  26. stopwatch.Stop();
  27. Console.WriteLine("Parallel cost " + stopwatch.ElapsedMilliseconds + " milliseconds");
写了两个循环,做了一些没有意义的事情,目的主要是为了消耗CPU时间,同理在main方法中调用,运行结果如下
  1. Normal:
  2. Normal cost 1682 milliseconds
  3. ----------------------------
  4. Parallel:
  5. Parallel cost 575 milliseconds

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

修改一下代码:

  1. object o=new object();
  2. long sum = 0;
  3. Stopwatch stopwatch = new Stopwatch();
  4. Console.WriteLine("Normal:");
  5. stopwatch.Start();
  6. for (int i = 0; i < 10000; i++)
  7. {
  8. for (int j = 0; j < 60000; j++)
  9. {
  10. //int sum = 0;
  11. //sum += i;
  12. sum++;
  13. }
  14. }
  15. stopwatch.Stop();
  16. Console.WriteLine("Normal cost " + stopwatch.ElapsedMilliseconds + " milliseconds");
  17. Console.WriteLine("----------------------------");
  18.  
  19. Console.WriteLine("Parallel:");
  20. stopwatch.Restart();
  21. Parallel.For(0, 10000, i =>
  22. {
  23. for (int j = 0; j < 60000; j++)
  24. {
  25. //int sum = 0;
  26. //sum += i;
  27. lock (o)
  28. {
  29. sum++;
  30. }
  31. }
  32. });
  33. stopwatch.Stop();
  34. Console.WriteLine("Parallel cost " + stopwatch.ElapsedMilliseconds + " milliseconds");
Parallel.For由于是并行运行的,所以会同时访问全局变量num,为了得到正确的结果,要加锁,此时来看看运行结果:
  1. Normal:
  2. Normal cost 2549 milliseconds
  3. ----------------------------
  4. Parallel:
  5. Parallel cost 21563 milliseconds
是不是大吃一惊啊?Parallel.For竟然用了21秒多,而for跟之前的差不多。这主要是由于并行同时访问全局变量,会出现任务的调度问题,大多数时间消耗在了任务的调度上面。

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

  1. Parallel.For(0, 100, i =>
  2. {
  3. Console.WriteLine(i);
  4. });
  5. for (int i = 0; i < 100; i++)
  6. {
  7. Console.WriteLine(i);
  8. }

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

3. Parallel.ForEach:执行 foreach(在 Visual Basic 中为 For Each)操作,其中在 IEnumerable 上可能会并行运行迭代(Executes a foreach (For Each in Visual Basic) operation on an IEnumerable in which iterations may run in parallel.)

这个方法跟Foreach方法很相似,看看使用方法

  1. List<string> myList = new List<string>();
  2. Parallel.ForEach(myList, p =>
  3. {
  4. DoSomething(p);
  5. });

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

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

在串行代码中我们break一下就搞定了,但是并行就不是这么简单了,不过没关系,在并行循环的委托参数中提供了一个ParallelLoopState类的实例,
该实例提供了Break和Stop方法来帮我们实现。
Break:告知 Parallel 循环应在系统方便的时候尽早停止执行当前迭代之外的迭代。
Stop:告知 Parallel 循环应在系统方便的时候尽早停止执行。
下面来写一段代码使用一下:
  1. ConcurrentBag<int> bag = new ConcurrentBag<int>();
  2. Stopwatch stopWatch = new Stopwatch();
  3.  
  4. stopWatch.Start();
  5. Parallel.For(0, 1000, (i, state) =>
  6. {
  7. if (bag.Count == 300)
  8. {
  9. state.Break();
  10. //state.Stop();
  11. return;
  12. }
  13. bag.Add(i);
  14. });
  15. stopWatch.Stop();
  16. Console.WriteLine("Bag count is " + bag.Count + ", " + stopWatch.ElapsedMilliseconds);

2. 异常处理

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

    1. try
    2. {
    3. Parallel.Invoke(RunOne, RunTwo);
    4. }
    5. catch (AggregateException aex)
    6. {
    7. foreach (var ex in aex.InnerExceptions)
    8. {
    9. Console.WriteLine(ex.Message
原文地址:https://www.cnblogs.com/zxtceq/p/7842856.html