5天玩轉C#并行和多線程程式設計 —— 第一天 認識Parallel
随着多核時代的到來,并行開發越來越展示出它的強大威力!使用并行程式,充分的利用系統資源,提高程式的性能。
在.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);
- 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:
- 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();
- for (int i = 0; i < 10000; i++)
- for (int j = 0; j < 60000; j++)
- int sum = 0;
- sum += i;
- Parallel.For(0, 10000, i =>
- });
寫了兩個循環,做了一些沒有意義的事情,目的主要是為了消耗CPU時間,同理在main方法中調用,運作結果如下
- Normal cost 1682 milliseconds
- Parallel cost 575 milliseconds
可以看到,Parallel.For所用的時間比單純的for快了1秒多,可見提升的性能是非常可觀的。那麼,是不是Parallel.For在任何時候都比for要快呢?答案當然是“不是”,要不然微軟還留着for幹嘛?
修改一下代碼:
- object o=new object();
- long sum = 0;
- //int sum = 0;
- //sum += i;
- sum++;
- lock (o)
Parallel.For由于是并行運作的,是以會同時通路全局變量num,為了得到正确的結果,要加鎖,此時來看看運作結果:
- Normal cost 2549 milliseconds
- 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++)
從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