5天玩轉C#並行和多線程編程 —— 第一天 認識Parallel
目錄
5天玩轉C#並行和多線程編程 —— 第一天 認識Parallel
5天玩轉C#並行和多線程編程 —— 第二天 並行集合和PLinq
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.");
- }
結果如下:
- 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");
- 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");
- Normal:
- Normal cost 2549 milliseconds
- ----------------------------
- Parallel:
- Parallel cost 21563 milliseconds
一直說並行,那么從哪里可以看出來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