Parallel.For 你可能忽视的一个非常实用的重载方法
2017-03-23 13:05
323 查看
说起Parallel.For大家都不会陌生,很简单,不就是一个提供并行功能的for循环吗? 或许大家平时使用到的差不多就是其中最简单的那个重载方法,而真实情况
下Parallel.For里面有14个重载,而其中那些比较复杂的重载方法,或许还有同学还不知道怎么用呢~~~ 刚好我最近我有应用场景了,给大家介绍介绍,废话不多说,
先给大家看一下这个并行方法的重载一览表吧。。。
一:遇到的场景
我遇到的场景是这样的,项目中有这样一个功能,这个功能需要根据多个维度对一组customerIDList进行筛选,最后求得多个维度所筛选出客户的并集,我举个
例子:现有8个维度:
1. 交易行为
2.营销活动
3.地区
4.新老客户
5.营销渠道
6.客户属性
7.客户分组
8.商品
每个维度都能筛选出一批customerid出来,然后对8组customerid求并集,这种场景很明显要提升性能的话,你必须要做并行处理,当然能够实现的方式有很多种,
比如我定义8个task<T>,然后使用WaitAll等待一下,最后再累计每个Result的结果就可以了,代码如下:
上面的代码的逻辑还是很简单的,我使用的是Task<T>的模式,当然你也可以用void形式的Task,然后在里面lock代码的时候对hashCustomerIDList进行
插入,实现起来也是非常简单的,我就不演示了,那下面的问题来了,有没有更爽更直接的方式,看人家看上去更有档次一点的方法,而且还要达到这种效果呢?
二:Parallel.For复杂重载
回到文章开头的话题,首先我们仔细分析一下下面这个复杂的重载方法。
从上面的代码区域中看,你可以看到上面提供了5个参数,而最后意思的就是后面三个,如果你对linq的扩展方法比较熟悉的话,你会发现这个其实就是一个并行版本
的累加器(Aggregate)操作,因为他们都是具有三个区域:第一个区域就是初始化区域(localInit),就是累积之前的一个初始化操作,第二个区域其实就是一个迭代
区域,说白了就是foreach/for循环,for循环之中,会把计算结果累计到当初初始化区域设置的变量中,第三个区域就是foreach/for之后的一个最终计算区,三者合起
来就是一个并行累加器,为了方便大家更好的理解,我就扒一下源码给大家看看:
由于图太大,就截两张图了,大家一定要仔细体会一下这里面的tlocal变量,因为这个tlocal的使用贯穿着三个区域,所以大家一定要好好体会下面这几句代码
当你理解了tLocal具有累积foreach中的item结果之后,你就应该很明白下面这个body=>(item, loop, total) 和 finally => (total) 中total的含义了,
对吧,当你明白了,然后大家可以看看下面这段代码,是不是用一个方法就搞定了原来需要分阶段实现的一个业务逻辑呢?
好了,本篇就先说这么多,希望这个具有并行累加器效果的Parallel.For能够给你带来一丝灵感~~~
下Parallel.For里面有14个重载,而其中那些比较复杂的重载方法,或许还有同学还不知道怎么用呢~~~ 刚好我最近我有应用场景了,给大家介绍介绍,废话不多说,
先给大家看一下这个并行方法的重载一览表吧。。。
一:遇到的场景
我遇到的场景是这样的,项目中有这样一个功能,这个功能需要根据多个维度对一组customerIDList进行筛选,最后求得多个维度所筛选出客户的并集,我举个
例子:现有8个维度:
1. 交易行为
2.营销活动
3.地区
4.新老客户
5.营销渠道
6.客户属性
7.客户分组
8.商品
每个维度都能筛选出一批customerid出来,然后对8组customerid求并集,这种场景很明显要提升性能的话,你必须要做并行处理,当然能够实现的方式有很多种,
比如我定义8个task<T>,然后使用WaitAll等待一下,最后再累计每个Result的结果就可以了,代码如下:
class Program { static void Main(string[] args) { List<string> rankList = Enum.GetNames(typeof(FilterType)).ToList(); Task<HashSet<int>>[] tasks = new Task<HashSet<int>>[rankList.Count]; var hashCustomerIDList = new HashSet<int>(); //求customerid的并集 for (int i = 0; i < tasks.Length; i++) { tasks[i] = Task.Factory.StartNew<HashSet<int>>((obj) => { //业务方法,耗损性能中。。。 var smallCustomerIDHash = GetXXXMethod(rankList[(int)obj]); return smallCustomerIDHash; }, i); } Task.WaitAll(tasks); foreach (var task in tasks) { foreach (var item in task.Result) { hashCustomerIDList.Add(item); } } } static HashSet<int> GetXXXMethod(string rank) { return new HashSet<int>(); } public enum FilterType { 交易行为 = 1, 营销活动 = 2, 地区 = 4, 新老客户 = 8, 营销渠道 = 16, 客户属性 = 32, 客户分组 = 64, 商品 = 128 } }
上面的代码的逻辑还是很简单的,我使用的是Task<T>的模式,当然你也可以用void形式的Task,然后在里面lock代码的时候对hashCustomerIDList进行
插入,实现起来也是非常简单的,我就不演示了,那下面的问题来了,有没有更爽更直接的方式,看人家看上去更有档次一点的方法,而且还要达到这种效果呢?
二:Parallel.For复杂重载
回到文章开头的话题,首先我们仔细分析一下下面这个复杂的重载方法。
// // 摘要: // 执行具有线程本地数据的 for(在 Visual Basic 中为 For)循环,其中可能会并行运行迭代,而且可以监视和操作循环的状态。 // // 参数: // fromInclusive: // 开始索引(含)。 // // toExclusive: // 结束索引(不含)。 // // localInit: // 用于返回每个任务的本地数据的初始状态的函数委托。 // // body: // 将为每个迭代调用一次的委托。 // // localFinally: // 用于对每个任务的本地状态执行一个最终操作的委托。 // // 类型参数: // TLocal: // 线程本地数据的类型。 // // 返回结果: // 包含有关已完成的循环部分的信息的结构。 // // 异常: // T:System.ArgumentNullException: // body 参数为 null。- 或 -localInit 参数为 null。- 或 -localFinally 参数为 null。 // // T:System.AggregateException: // 包含在所有线程上引发的全部单个异常的异常。 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的使用贯穿着三个区域,所以大家一定要好好体会下面这几句代码
TLocal tLocal = default(TLocal); tLocal = localInit(); while(xxx<xxx){ tLocal = bodyWithLocal(num5, parallelLoopState, tLocal); } localFinally(tLocal);
当你理解了tLocal具有累积foreach中的item结果之后,你就应该很明白下面这个body=>(item, loop, total) 和 finally => (total) 中total的含义了,
对吧,当你明白了,然后大家可以看看下面这段代码,是不是用一个方法就搞定了原来需要分阶段实现的一个业务逻辑呢?
class Program { static void Main(string[] args) { List<string> rankList = Enum.GetNames(typeof(FilterType)).ToList(); var hashCustomerIDList = new HashSet<int>(); //求customerid的并集 //并行计算 7个 维度的 总和 Parallel.For(0, rankList.Count, () => { return new List<int>(); }, (item, loop, total) => { //业务方法,耗损性能中。。。 var smallCustomerIDHash = GetXXXMethod(rankList[item]); total.AddRange(smallCustomerIDHash); return total; }, (total) => { lock (hashCustomerIDList) { foreach (var customerID in total) { hashCustomerIDList.Add(customerID); } } }); } static HashSet<int> GetXXXMethod(string rank) { return new HashSet<int>(); } public enum FilterType { 交易行为 = 1, 营销活动 = 2, 地区 = 4, 新老客户 = 8, 营销渠道 = 16, 客户属性 = 32, 客户分组 = 64, 商品 = 128 } }
好了,本篇就先说这么多,希望这个具有并行累加器效果的Parallel.For能够给你带来一丝灵感~~~
相关文章推荐
- Parallel.For 你可能忽视的一个非常实用的重载方法
- asp.net 自己封装数据库操作一个类中一个自定义方法Execute(),非常实用,省去了麻烦的中间过程,动态参数
- 一个简单不过却很非常实用的PHP加密字符串方法
- 今天发现了c++符号重载的一个非常实用的使用实例。
- 南阳11 直接打印这是一个非常实用的方法,但细节的换行符要注意
- 一个简单实用的ASP调试函数和使用方法
- [C++程序设计]一个非常不错的函数设计方法——空存根
- 一个非常实用的javascript读写Cookie函数
- 一个可能破解所有公钥密码的方法
- 一个仿QQ导航菜单,非常实用!
- 一个非常实用的javascript读写Cookie函数
- 一个可以加快Oracle查询语句性能的可能方法
- [转贴]一个仿QQ导航菜单,非常实用!
- 一个非常实用的Base64类
- 一个非常实用的javascript读写Cookie函数
- 一个非常实用的 div 容器样式
- 关于Qt编译时问题的一个非常奇怪的解决方法
- 使用ld的“-Ttext”选项时可能产生的一个问题的解决方法
- 一个非常方便的查看当前页源码的方法:
- epoll使用的一个小例子,非常实用