Parallel.For 你可能忽視的一個非常實用的過載方法

一線碼農發表於2017-03-23

  說起Parallel.For大家都不會陌生,很簡單,不就是一個提供並行功能的for迴圈嗎? 或許大家平時使用到的差不多就是其中最簡單的那個過載方法,而真實情況

下Parallel.For裡面有14個過載,而其中那些比較複雜的過載方法,或許還有同學還不知道怎麼用呢~~~ 剛好我最近我有應用場景了,給大家介紹介紹,廢話不多說,

先給大家看一下這個並行方法的過載一覽表吧。。。

 

一:遇到的場景

     我遇到的場景是這樣的,專案中有這樣一個功能,這個功能需要根據多個維度對一組customerIDList進行篩選,最後求得多個維度所篩選出客戶的並集,我舉個

例子:現有8個維度:

1. 交易行為

2.營銷活動

3.地區

4.新老客戶

5.營銷渠道

6.客戶屬性

7.客戶分組

8.商品

每個維度都能篩選出一批customerid出來,然後對8組customerid求並集,這種場景很明顯要提升效能的話,你必須要做並行處理,當然能夠實現的方式有很多種,

比如我定義8個task<T>,然後使用WaitAll等待一下,最後再累計每個Result的結果就可以了,程式碼如下:

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     {
 5         List<string> rankList = Enum.GetNames(typeof(FilterType)).ToList();
 6 
 7         Task<HashSet<int>>[] tasks = new Task<HashSet<int>>[rankList.Count];
 8 
 9         var hashCustomerIDList = new HashSet<int>();  //求customerid的並集
10 
11         for (int i = 0; i < tasks.Length; i++)
12         {
13             tasks[i] = Task.Factory.StartNew<HashSet<int>>((obj) =>
14             {
15                 //業務方法,耗損效能中。。。
16                 var smallCustomerIDHash = GetXXXMethod(rankList[(int)obj]);
17 
18                 return smallCustomerIDHash;
19             }, i);
20         }
21 
22         Task.WaitAll(tasks);
23 
24         foreach (var task in tasks)
25         {
26             foreach (var item in task.Result)
27             {
28                 hashCustomerIDList.Add(item);
29             }
30         }
31     }
32 
33     static HashSet<int> GetXXXMethod(string rank)
34     {
35         return new HashSet<int>();
36     }
37 
38     public enum FilterType
39     {
40         交易行為 = 1,
41         營銷活動 = 2,
42         地區 = 4,
43         新老客戶 = 8,
44         營銷渠道 = 16,
45         客戶屬性 = 32,
46         客戶分組 = 64,
47         商品 = 128
48     }
49 }

      上面的程式碼的邏輯還是很簡單的,我使用的是Task<T>的模式,當然你也可以用void形式的Task,然後在裡面lock程式碼的時候對hashCustomerIDList進行

插入,實現起來也是非常簡單的,我就不演示了,那下面的問題來了,有沒有更爽更直接的方式,看人家看上去更有檔次一點的方法,而且還要達到這種效果呢?

 

二:Parallel.For複雜過載

 回到文章開頭的話題,首先我們仔細分析一下下面這個複雜的過載方法。

 1  //
 2         // 摘要:
 3         //     執行具有執行緒本地資料的 for(在 Visual Basic 中為 For)迴圈,其中可能會並行執行迭代,而且可以監視和操作迴圈的狀態。
 4         //
 5         // 引數:
 6         //   fromInclusive:
 7         //     開始索引(含)。
 8         //
 9         //   toExclusive:
10         //     結束索引(不含)。
11         //
12         //   localInit:
13         //     用於返回每個任務的本地資料的初始狀態的函式委託。
14         //
15         //   body:
16         //     將為每個迭代呼叫一次的委託。
17         //
18         //   localFinally:
19         //     用於對每個任務的本地狀態執行一個最終操作的委託。
20         //
21         // 型別引數:
22         //   TLocal:
23         //     執行緒本地資料的型別。
24         //
25         // 返回結果:
26         //     包含有關已完成的迴圈部分的資訊的結構。
27         //
28         // 異常:
29         //   T:System.ArgumentNullException:
30         //     body 引數為 null。- 或 -localInit 引數為 null。- 或 -localFinally 引數為 null。
31         //
32         //   T:System.AggregateException:
33         //     包含在所有執行緒上引發的全部單個異常的異常。
34         public static ParallelLoopResult For<TLocal>(int fromInclusive, int toExclusive, Func<TLocal> localInit, Func<int, ParallelLoopState, TLocal, TLocal> body, Action<TLocal> localFinally);

 

從上面的程式碼區域中看,你可以看到上面提供了5個引數,而最後意思的就是後面三個,如果你對linq的擴充套件方法比較熟悉的話,你會發現這個其實就是一個並行版本

的累加器(Aggregate)操作,因為他們都是具有三個區域:第一個區域就是初始化區域(localInit),就是累積之前的一個初始化操作,第二個區域其實就是一個迭代

區域,說白了就是foreach/for迴圈,for迴圈之中,會把計算結果累計到當初初始化區域設定的變數中,第三個區域就是foreach/for之後的一個最終計算區,三者合起

來就是一個並行累加器,為了方便大家更好的理解,我就扒一下原始碼給大家看看:

 

 由於圖太大,就截兩張圖了,大家一定要仔細體會一下這裡面的tlocal變數,因為這個tlocal的使用貫穿著三個區域,所以大家一定要好好體會下面這幾句程式碼

1 TLocal tLocal = default(TLocal);
2 
3 tLocal = localInit();
4 
5 while(xxx<xxx){
6 tLocal = bodyWithLocal(num5, parallelLoopState, tLocal);
7 }
8 localFinally(tLocal);

 

      當你理解了tLocal具有累積foreach中的item結果之後,你就應該很明白下面這個body=>(item, loop, total) 和 finally => (total) 中total的含義了,

對吧,當你明白了,然後大家可以看看下面這段程式碼,是不是用一個方法就搞定了原來需要分階段實現的一個業務邏輯呢?

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     {
 5         List<string> rankList = Enum.GetNames(typeof(FilterType)).ToList();
 6 
 7         var hashCustomerIDList = new HashSet<int>();  //求customerid的並集
 8 
 9         //平行計算 7個 維度的 總和
10         Parallel.For(0, rankList.Count, () => { return new List<int>(); }, (item, loop, total) =>
11         {
12             //業務方法,耗損效能中。。。
13             var smallCustomerIDHash = GetXXXMethod(rankList[item]);
14 
15             total.AddRange(smallCustomerIDHash);
16 
17             return total;
18         }, (total) =>
19         {
20             lock (hashCustomerIDList)
21             {
22                 foreach (var customerID in total)
23                 {
24                     hashCustomerIDList.Add(customerID);
25                 }
26             } 
27         });
28     }
29 
30     static HashSet<int> GetXXXMethod(string rank)
31     {
32         return new HashSet<int>();
33     }
34 
35     public enum FilterType
36     {
37         交易行為 = 1,
38         營銷活動 = 2,
39         地區 = 4,
40         新老客戶 = 8,
41         營銷渠道 = 16,
42         客戶屬性 = 32,
43         客戶分組 = 64,
44         商品 = 128
45     }
46 }

   好了,本篇就先說這麼多,希望這個具有並行累加器效果的Parallel.For能夠給你帶來一絲靈感~~~

相關文章