您的位置:首页 > 编程语言 > C#

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

2016-10-29 16:00 441 查看
5天玩转C#并行和多线程编程 —— 第一天 认识Parallel

C#

原文: 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)

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

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

static void Main(string[] args)

{

#region Demo1

Stopwatch stopwatch = new Stopwatch();

Console.WriteLine("Normal:");

stopwatch.Start();

RunOne();

RunTwo();

stopwatch.Stop();

Console.WriteLine("Normal cost " + stopwatch.ElapsedMilliseconds + " milliseconds");

Console.WriteLine("----------------------------");

Console.WriteLine("Parallel:");

stopwatch.Restart();

Parallel.Invoke(RunOne, RunTwo);

stopwatch.Stop();

Console.WriteLine("Parallel cost " + stopwatch.ElapsedMilliseconds + " milliseconds");

#endregion

Console.ReadKey();

}

static void RunOne()

{

Thread.Sleep(2000);

Console.WriteLine("The RunOne cost 2 seconds.");

}

static void RunTwo()

{

Thread.Sleep(3000);

Console.WriteLine("The RunTwo cost 3 seconds.");

}

代码很简单,分别写了RunOne和RunTwo方法,等待一定的时间输出一句话,然后再main方法中用Stopwatch这个类来记录运行的总毫秒数,来比较串行和并行的运行效率

结果如下:

Normal:

The RunOne cost 2 seconds.

The RunTwo cost 3 seconds.

Normal cost 5001 milliseconds

----------------------------

Parallel:

The RunOne cost 2 seconds.

The RunTwo cost 3 seconds.

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循环功能相似,来写个例子看一下吧,还是控制台程序

Stopwatch stopwatch=new Stopwatch();

Console.WriteLine("Normal:");

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("Normal cost " + stopwatch.ElapsedMilliseconds + " milliseconds");

Console.WriteLine("----------------------------");

Console.WriteLine("Parallel:");

stopwatch.Restart();

Parallel.For(0, 10000, i =>

{

for (int j = 0; j < 60000; j++)

{

int sum = 0;

sum += i;

}

});

stopwatch.Stop();

Console.WriteLine("Parallel cost " + stopwatch.ElapsedMilliseconds + " milliseconds");

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

Normal:

Normal cost 1682 milliseconds

----------------------------

Parallel:

Parallel cost 575 milliseconds

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

修改一下代码:

object o=new object();

long sum = 0;

Stopwatch stopwatch = new Stopwatch();

Console.WriteLine("Normal:");

stopwatch.Start();

for (int i = 0; i < 10000; i++)

{

for (int j = 0; j < 60000; j++)

{

//int sum = 0;

//sum += i;

sum++;

}

}

stopwatch.Stop();

Console.WriteLine("Normal cost " + stopwatch.ElapsedMilliseconds + " milliseconds");

Console.WriteLine("----------------------------");

Console.WriteLine("Parallel:");

stopwatch.Restart();

Parallel.For(0, 10000, i =>

{

for (int j = 0; j < 60000; j++)

{

//int sum = 0;

//sum += i;

lock (o)

{

sum++;

}

}

});

stopwatch.Stop();

Console.WriteLine("Parallel cost " + stopwatch.ElapsedMilliseconds + " milliseconds");

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

Normal cost 2549 milliseconds

----------------------------

Parallel:

Parallel cost 21563 milliseconds

是不是大吃一惊啊?Parallel.For竟然用了21秒多,而for跟之前的差不多。这主要是由于并行同时访问全局变量,会出现任务的调度问题,大多数时间消耗在了任务的调度上面。

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

Parallel.For(0, 100, i =>

{

Console.WriteLine(i);

});

for (int i = 0; i < 100; i++)

{

Console.WriteLine(i);

}

从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方法很相似,看看使用方法

List<string> myList = new List<string>();

Parallel.ForEach(myList, p =>

{

DoSomething(p);

});

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

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

在串行代码中我们break一下就搞定了,但是并行就不是这么简单了,不过没关系,在并行循环的委托参数中提供了一个ParallelLoopState类的实例,
该实例提供了Break和Stop方法来帮我们实现。
Break:告知 Parallel 循环应在系统方便的时候尽早停止执行当前迭代之外的迭代。
Stop:告知 Parallel 循环应在系统方便的时候尽早停止执行。
下面来写一段代码使用一下:

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

Stopwatch stopWatch = new Stopwatch();

stopWatch.Start();

Parallel.For(0, 1000, (i, state) =>

{

if (bag.Count == 300)

{

state.Break();

//state.Stop();

return;

}

bag.Add(i);

});

stopWatch.Stop();

Console.WriteLine("Bag count is " + bag.Count + ", " + stopWatch.ElapsedMilliseconds);

2. 异常处理

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

try

{

Parallel.Invoke(RunOne, RunTwo);

}

catch (AggregateException aex)

{

foreach (var ex in aex.InnerExceptions)

{

Console.WriteLine(ex.Message
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: