• Parallel.For 你可能忽视的一个非常实用的重载方法


      说起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能够给你带来一丝灵感~~~

  • 相关阅读:
    Windows 驱动加载程序代码
    coding push 上传文件
    MinGW 可以编译驱动的
    通过使用 NTLite 工具实现精简Windows系统
    一些常用的注入技巧
    Python 图片转字符图
    MySQL数据库及注入方法
    路由器被蹭网后,我有被黑的风险吗?
    markdown 实现代码折叠效果
    Windows 签名伪造工具的使用,Python,签名
  • 原文地址:https://www.cnblogs.com/huangxincheng/p/6604553.html
Copyright © 2020-2023  润新知