parallel
英 [ˈpærəlel] 美 [ˈpærəˌlɛl]
adj.平行的; 相同的,相似的; [電]並聯的; [計]並行的
adv.平行地,並列地
n.平行線(面); 類似物; 類比; 緯線
vt.使平行; 與…媲美; 與…相比; 與…類似算法
Parallel
主要提供了 For
系列方法和 ForEach
系列方法,並行處理全部項。兩個系列的方法都有若干重載,但最經常使用也最易用的是這兩個安全
Parallel.For(int fromInclude, int toExclude, Action<int> body)
這個方法從 fromInclude
開始,到 toExclude
結束,循環其中的每個數,並對其執行 body
中的代碼。從參數的名稱能夠了解,這些數,包含了 fromInclude
,但不包含 toExclude
。異步
這很像 for(int i = fromInclude; i < toExclude; i++) { body(i) }
,但與之不一樣的是 for
語句會順序的遍歷每個數,而 Parallel.For
會盡量的同時處理這些數——它是異步的,也就意味着,它是無序的。學習
來舉個簡單的例子pwa
Parallel.For(0, 10, (i) => { Console.Write($"{i} "); }); Console.WriteLine();
下面是它可能的輸出之一(由於無序,因此並不肯定)線程
0 4 8 9 1 3 5 6 2 7
Parallel.ForEach<T>(IEnumerable<T>, Action<T>)
Parallel.For
就是異步的 for
,那麼 Parallel.ForEach
就是異步的 foreach
。仍是剛纔那個例子,稍稍改動下code
var all = new [] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; Parallel.ForEach(all, (i) => { Console.Write($"{i} "); }); Console.WriteLine();
其結果一樣是 0~9 的無序排列輸出。繼承
上面僅僅說明了 Parallel.For
和 Parallel.ForEach
,然並卵,遠不如原生的 for
和 foreach
易用。純粹從語法上來講,是的,可是要了解到 Parallel
的目的是並行,並行的目的是高效處理耗時計算。因此,如今須要假設一個耗計算存在……遞歸
void LongCalc(int n) { // 也沒什麼用,只是模擬耗時而已 Thread.Sleep(n * 1000); }
若是用 for
循環,不用運行也能知道下面這代碼運行時間會超過 6000 毫秒。it
for (int i = 1; i < 4; i++) { LongCalc(i); }
可是用 Parallel.For
呢
Stopwatch watch = new Stopwatch(); watch.Start(); Parallel.For(1, 4, LongCalc); watch.Stop(); Console.WriteLine(watch.ElapsedMilliseconds);
答案是 3019
,意料之中,並行嘛!
body
調用的計算結果怎麼辦這個問題又站在了一個新的高度,畢竟不全部事情都不須要反饋。
如今用一個遞歸調用的階乘算法來模擬耗時計算,雖然它其實並不怎麼耗時,爲了使代碼簡單,這裏並不會選用太大的數。而後假設須要算 n 個數的階乘之和,因此咱們須要並行計算每一個數的階乘,再把各個結果加起來。這段代碼大概會是這樣
int CalcFactorial(int n) { return n <= 2 ? n : n * CalcFactorial(n - 1); } int SumFactorial(params int[] data) { int sum = 0; Parallel.ForEach(data, n => { sum += CalcFactorial(n); }); return sum; }
給幾個數就能夠獲得結果:
Console.WriteLine($"sum is {SumFactorial(4, 5, 7, 9)}");
猜猜結果是多少?
368064
麼?是的。
368064
,但並不每次都是,有時候可能會獲得一個 120
,或者 5040
,爲何???
注意「並行」,這意味着存在線程安全的問題。+=
並非原子操做,因此這裏須要改一下
int SumFactorial(params int[] data) { int sum = 0; Parallel.ForEach(data, n => { Interlocked.Add(ref sum, CalcFactorial(n)); }); return sum; }
如你如願,這回對了。Interlocked
類提供了一些簡單計算的原子操做,徹底值得去學習一下。
話雖如此,但須要的不是一個計算好的結果,而是每個單獨的結果怎麼辦?看起來 Parallel
有點不合適了,那就試試 ParallelQuery
吧,這個來自 Linq 的東東。
IEnumerable<T>.AsParallel()
能夠很容易獲得 ParallelQuery<T>
,這也是 Linq 中提供的擴展方法。那麼從熟悉的開始,改用 ParallelQuery<T>
來算算階乘之和
int SumFactorial(params int[] data) { return data.AsParallel().Select(CalcFactorial).Sum(); }
很簡單的樣子。並且 Select()
也很熟悉,它獲得的是一個 ParallelQuery<T>
,繼承自 IEnumerable<T>
。因此,若是須要每個單獨的結果,只要去掉 Sum()
,換成 ToList()
或者 ToArray()
就能夠了,甚至直接做爲一個 IEnummerable<T>
來使用也是不錯的選擇。
這裏彷佛接觸到了一個新的話題——並行 Linq。其實 Linq 也是編譯成方法調用來運行的,如今已經有方法調用的代碼了,寫個 Linq 語句還不容易:
int SumFactorial(params int[] data) { return (from n in data.AsParallel() select CalcFactorial(n)).Sum(); }