网页制作合同样本,长沙网站seo技术,中细软做的网站,嘉兴网站制作案例在并行编程中#xff0c;经常会遇到多线程间操作共享集合的问题#xff0c;很多时候大家都很难逃避这个问题做到一种无锁编程状态#xff0c;你也知道一旦给共享集合套上lock之后#xff0c;并发和伸缩能力往往会造成很大影响#xff0c;这篇就来谈谈如何尽可能的减少lock… 在并行编程中经常会遇到多线程间操作共享集合的问题很多时候大家都很难逃避这个问题做到一种无锁编程状态你也知道一旦给共享集合套上lock之后并发和伸缩能力往往会造成很大影响这篇就来谈谈如何尽可能的减少lock锁次数甚至没有。一缘由1. 业务背景昨天在review代码的时候看到以前自己写的这么一段代码精简后如下 private static Listlong ExecuteFilterList(int shopID, ListMemoryCacheTrade trades, ListFilterConditon filterItemList, MatrixSearchContext searchContext){var customerIDList new Listlong();var index 0;Parallel.ForEach(filterItemList, new ParallelOptions() { MaxDegreeOfParallelism 4 },(filterItem) {var context new FilterItemContext(){StartTime searchContext.StartTime,EndTime searchContext.EndTime,ShopID shopID,Field filterItem.Field,FilterType filterItem.FilterType,ItemList filterItem.FilterValue,SearchList trades.ToList()};var smallCustomerIDList context.Execute();lock (filterItemList){if (index 0){customerIDList.AddRange(smallCustomerIDList);index;}else{customerIDList customerIDList.Intersect(smallCustomerIDList).ToList();}}});return customerIDList;}
这段代码实现的功能是这样的filterItemList承载着所有原子化的筛选条件然后用多线程的形式并发执行里面的item最后将每个item获取的客户人数集合在高层进行整体求交画个简图就是下面这样。2. 问题分析其实这代码存在着一个很大的问题在Parallel中直接使用lock锁的话filterItemList有多少个我的lock就会锁多少次这对并发和伸缩性是有一定影响的现在就来想想怎么优化吧3. 测试案例为了方便演示我模拟了一个小案例方便大家看到实时结果修改后的代码如下 public static void Main(string[] args){var filterItemList new Liststring() { conditon1, conditon2, conditon3, conditon4, conditon5, conditon6 };ParallelTest1(filterItemList);}public static void ParallelTest1(Liststring filterItemList){var totalCustomerIDList new Listint();bool isfirst true;Parallel.ForEach(filterItemList, new ParallelOptions() { MaxDegreeOfParallelism 2 }, (query) {var smallCustomerIDList GetCustomerIDList(query);lock (filterItemList){if (isfirst){totalCustomerIDList.AddRange(smallCustomerIDList);isfirst false;}else{totalCustomerIDList totalCustomerIDList.Intersect(smallCustomerIDList).ToList();}Console.WriteLine(${DateTime.Now} 被锁了);}});Console.WriteLine($最后交集客户ID:{string.Join(,, totalCustomerIDList)});}public static Listint GetCustomerIDList(string query){var dict new Dictionarystring, Listint(){[conditon1] new Listint() { 1, 2, 4, 7 },[conditon2] new Listint() { 1, 4, 6, 7 },[conditon3] new Listint() { 1, 4, 5, 7 },[conditon4] new Listint() { 1, 2, 3, 7 },[conditon5] new Listint() { 1, 2, 4, 5, 7 },[conditon6] new Listint() { 1, 3, 4, 7, 9 },};return dict[query];}------ output ------
2020/04/21 15:53:34 被锁了
2020/04/21 15:53:34 被锁了
2020/04/21 15:53:34 被锁了
2020/04/21 15:53:34 被锁了
2020/04/21 15:53:34 被锁了
2020/04/21 15:53:34 被锁了
最后交集客户ID:1,7
二第一次优化从结果中可以看到filterItemList有6个锁次数也是6次那如何降低呢其实实现Parallel代码的FCL大神也考虑到了这个问题从底层给了一个很好的重载如下所示
public static ParallelLoopResult ForEachTSource, TLocal(OrderablePartitionerTSource source, ParallelOptions parallelOptions, FuncTLocal localInit, FuncTSource, ParallelLoopState, long, TLocal, TLocal body, ActionTLocal localFinally);
这个重载很特别多了两个参数localInit和localFinally,过会说一下什么意思先看修改后的代码体会一下public static void ParallelTest2(Liststring filterItemList){var totalCustomerIDList new Listint();var isfirst true;Parallel.ForEachstring, Listint(filterItemList,new ParallelOptions() { MaxDegreeOfParallelism 2 },() { return null; },(query, loop, index, smalllist) {var smallCustomerIDList GetCustomerIDList(query);if (smalllist null) return smallCustomerIDList;return smalllist.Intersect(smallCustomerIDList).ToList();},(finalllist) {lock (filterItemList){if (isfirst){totalCustomerIDList.AddRange(finalllist);isfirst false;}else{totalCustomerIDList totalCustomerIDList.Intersect(finalllist).ToList();}Console.WriteLine(${DateTime.Now} 被锁了);}});Console.WriteLine($最后交集客户ID:{string.Join(,, totalCustomerIDList)});}------- output ------
2020/04/21 16:11:46 被锁了
2020/04/21 16:11:46 被锁了
最后交集客户ID:1,7
Press any key to continue . . .
很好这次优化将lock次数从6次降到了2次这里我用了 new ParallelOptions() { MaxDegreeOfParallelism 2 } 设置了并发度为最多2个CPU核程序跑起来后会开两个线程将一个大集合划分为2个小集合相当于1个集合3个条件第一个线程在执行3个条件的起始处会执行你的localInit函数在3个条件迭代完之后再执行你的localFinally第二个线程也是按照同样方式执行自己的3个条件说的有点晦涩画一张图说明吧。三第二次优化如果你了解Task\这种带有返回值的Task这就好办了多少个filterItemList就可以开多少个Task反正Task底层是使用线程池承载的所以不用怕这样就完美的实现无锁编程。public static void ParallelTest3(Liststring filterItemList){var totalCustomerIDList new Listint();var tasks new TaskListint[filterItemList.Count];for (int i 0; i filterItemList.Count; i){tasks[i] Task.Factory.StartNew((query) {return GetCustomerIDList(query.ToString());}, filterItemList[i]);}Task.WaitAll(tasks);for (int i 0; i tasks.Length; i){var smallCustomerIDList tasks[i].Result;if (i 0){totalCustomerIDList.AddRange(smallCustomerIDList);}else{totalCustomerIDList totalCustomerIDList.Intersect(smallCustomerIDList).ToList();}}Console.WriteLine($最后交集客户ID:{string.Join(,, totalCustomerIDList)});}------ output -------最后交集客户ID:1,7
Press any key to continue . . .
四总结我们将原来的6个lock优化到了无锁编程但并不说明无锁编程就一定比带有lock的效率高大家要结合自己的使用场景合理的使用和混合搭配。好了本篇就说到这里希望对您有帮助。