5天玩轉C#並行和多線程編程系列文章目錄html
5天玩轉C#並行和多線程編程 —— 第一天 認識Parallel編程
5天玩轉C#並行和多線程編程 —— 次日 並行集合和PLinq多線程
5天玩轉C#並行和多線程編程 —— 第三天 認識和使用Taskoop
5天玩轉C#並行和多線程編程 —— 第四天 Task進階post
5天玩轉C#並行和多線程編程 —— 第五天 多線程編程大總結性能
隨着多核時代的到來,並行開發愈來愈展現出它的強大威力!使用並行程序,充分的利用系統資源,提升程序的性能。在.net 4.0中,微軟給咱們提供了一個新的命名空間:System.Threading.Tasks。這裏面有不少關於並行開發的東西,今天第一篇就介紹下最基礎,最簡單的——認識和使用Parallel。測試
在Parallel下面有三個經常使用的方法invoke,For和ForEach。url
一、 Parallel.Invokespa
這是最簡單,最簡潔的將串行的代碼並行化。.net
在這裏先講一個知識點,就是StopWatch的使用,最近有一些人說找不到StopWatch,StopWatch究竟是什麼東西,今天就來講明一下。
StopWatch在System.Diagnostics命名控件,要使用它就要先引用這個命名空間。
其使用方法以下:
var stopWatch = new StopWatch(); //建立一個Stopwatch實例
stopWatch.Start(); //開始計時
stopWatch.Stop(); //中止計時
stopWatch.Reset(); //重置StopWatch
stopWatch.Restart(); //從新啓動被中止的StopWatch
stopWatch.ElapsedMilliseconds //獲取stopWatch從開始到如今的時間差,單位是毫秒
本次用到的就這麼多知識點,想了解更多關於StopWatch的,去百度一下吧,網上有不少資料。
下面進入總體,開始介紹Parallel.Invoke方法,廢話很少說了,首先新建一個控制檯程序,添加一個類,代碼以下:
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秒,也就是耗時最長的那個方法,能夠看出方法是並行執行的,執行效率提升了不少。
二、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 + "\t"); });
從0輸出到99,運行後會發現輸出的順序不對,用for順序確定是對的,並行同時執行,因此會出現輸出順序不一樣的狀況。
二、Parallel.Foreach
這個方法跟Foreach方法很類似,想具體瞭解的,能夠百度些資料看看,這裏就很少說了,下面給出其使用方法:
List<int> list = new List<int>(); list.Add(0); Parallel.ForEach(list, item => { DoWork(item); });
一、當咱們使用到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個,你們能夠測試一下。
二、異常處理
首先任務是並行計算的,處理過程當中可能會產生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循環遍歷輸出異常信息,能夠看到兩個異常信息都顯示了。