在并行编程中,经常会遇到多线程间操作共享集合的问题,很多时候大家都很难逃避这个问题做到一种无锁编程状态,你也知道一旦给共享集合套上lock之后,并发和伸缩能力往往会造成很大影响,这篇就来谈谈如何尽可能的减少lock锁次数甚至没有。

一:缘由

1. 业务背景

昨天在review代码的时候,看到以前自己写的这么一段代码,精简后如下:

        private static List<long> ExecuteFilterList(int shopID, List<MemoryCacheTrade> trades, List<FilterConditon> filterItemList, MatrixSearchContext searchContext){var customerIDList = new List<long>();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 List<string>() { "conditon1", "conditon2", "conditon3", "conditon4", "conditon5", "conditon6" };ParallelTest1(filterItemList);}public static void ParallelTest1(List<string> filterItemList){var totalCustomerIDList = new List<int>();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 List<int> GetCustomerIDList(string query){var dict = new Dictionary<string, List<int>>(){["conditon1"] = new List<int>() { 1, 2, 4, 7 },["conditon2"] = new List<int>() { 1, 4, 6, 7 },["conditon3"] = new List<int>() { 1, 4, 5, 7 },["conditon4"] = new List<int>() { 1, 2, 3, 7 },["conditon5"] = new List<int>() { 1, 2, 4, 5, 7 },["conditon6"] = new List<int>() { 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 ForEach<TSource, TLocal>(OrderablePartitioner<TSource> source, ParallelOptions parallelOptions, Func<TLocal> localInit, Func<TSource, ParallelLoopState, long, TLocal, TLocal> body, Action<TLocal> localFinally);

这个重载很特别,多了两个参数localInit和localFinally,过会说一下什么意思,先看修改后的代码体会一下

public static void ParallelTest2(List<string> filterItemList){var totalCustomerIDList = new List<int>();var isfirst = true;Parallel.ForEach<string, List<int>>(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(List<string> filterItemList){var totalCustomerIDList = new List<int>();var tasks = new Task<List<int>>[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的效率高,大家要结合自己的使用场景合理的使用和混合搭配。

好了,本篇就说到这里,希望对您有帮助。

我是如何一步步的在并行编程中将lock锁次数降到最低实现无锁编程相关推荐

  1. 帖子如何实现显示浏览次数_我是如何一步步的在并行编程中将lock锁次数降到最低实现无锁编程...

    在并行编程中,经常会遇到多线程间操作共享集合的问题,很多时候大家都很难逃避这个问题做到一种无锁编程状态,你也知道一旦给共享集合套上lock之后,并发和伸缩能力往往会造成很大影响,这篇就来谈谈如何尽可能 ...

  2. 一种云化busybox demolets的设想和一种根本降低编程实践难度的设想:免部署无语法编程

    本文关键字:shell language,debuginbuilt+google oriented programming practise+drive.programming:dgv program ...

  3. java无锁消费者框架_无锁并行框架多生产者多消费者模型

    下面看一下多生产多消费者的模式,下面的代码是模拟100个生产者,每个生产者生产100个事件,然后有3个消费者,同时进行消费,共消费1W个事件, 下面看一下代码: 这边new出了3个消费者,并把消费者数 ...

  4. 《C++ Concurrency in Action》笔记28 无锁并行数据结构

    7 设计无锁并行数据结构 mutex是一种强大的工具,可以保证多个线程安全访问数据结构.使用mutex的目的很直接:访问被保护数据的代码要么锁定了mutex,要么没有.然而,它也有不好的一面,错误的使 ...

  5. 无锁编程(Lock Free)框架 系列文章

    无锁编程(Lock Free)框架 系列文章: 1 前置知识:伪共享 原理 & 实战 2 disruptor 使用和原理 图解 3 akka 使用和原理 图解 4 camel 使用和 原理 图 ...

  6. matlab rad2deg,在R编程中将弧度值转换为度值–rad2deg()函数

    R语言中的rad2deg()函数用于将指定的弧度值转换为度数值.注意:此函数需要安装"grid"包.语法:rad2deg(x)参数:x:要转换的弧度值示例1:filter_none ...

  7. SQL注入—我是如何一步步攻破一家互联网公司的

    最近在研究Web安全相关的知识,特别是SQL注入类的相关知识.接触了一些与SQL注入相关的工具.周末在家闲着无聊,想把平时学的东东结合起来攻击一下身边某个小伙伴去的公司,看看能不能得逞.不试不知道,一 ...

  8. 我是如何一步步编码完成万仓网ERP系统的(三)登录

    https://www.cnblogs.com/smh188/p/11533668.html(我是如何一步步编码完成万仓网ERP系统的(一)系统架构) https://www.cnblogs.com/ ...

  9. 15个热门的编程趋势及15个逐步走向衰落的编程方向

    Peter Wayner是InfoWorld的一名特约编辑,也是一个多产的作家.除了InfoWorld之外,他还经常为纽约时报和连线杂志撰写文章.近日,Peter撰写了一篇文章,谈到了未来15个热门的 ...

最新文章

  1. js中的if与Java中的if_JavaScript if...else 语句
  2. Ogre貌似开始推荐MYGUI了~~
  3. burpsuite配置指南
  4. 2020——网鼎杯 (青龙组)jocker
  5. 自动平衡男女比例的随机分组软件B2G使用教程,献给组织
  6. 12人类为什么有战争
  7. 苹果6s最大屏幕尺寸_iPhone 6s:经典的小屏旗舰,百元价位也能做苹果党
  8. 从零基础转行到前端大牛,需要经过哪几个阶段?
  9. MySQL和Oracle的一些区别
  10. phpstorm2017破解方法
  11. 梅小雨20190919-5 代码规范,结对
  12. envi插件大津法_IDL处理Himawari8-NC数据
  13. matlab对5个矩阵循环求均值,MATLAB循环求数组的平均值 每隔几个数据求一下平均值...
  14. 2d unity 多物体 射线_Unity3D 之射线检测
  15. matlab中用plot函数绘制的常用设置以及五点三次平滑法的实现
  16. 携程测试经理网盘爆出面试题!!!【内附答案】
  17. 风力机叶片气动设计 matlab 程序,基于MATLAB的小型风力机叶片设计
  18. 从零双排学java之数组
  19. 快速理解-设计模式六大原则
  20. CPI公式 CPI含义 CPI意义 CPI什么意思

热门文章

  1. FPGA FIFO深度计算
  2. 【转】js老生常谈之this,constructor ,prototype
  3. iOS9 Storyboard unwind segue反回传递事件时机详细步骤
  4. 4月12日 webform基本控件
  5. Android 的基本组件之一 Gallery
  6. 记事本状态栏不会自动_如何在记事本中同时启用状态栏和自动换行
  7. 大白话5分钟带你走进人工智能-第二十节逻辑回归和Softmax多分类问题(5)
  8. Spring Cloud Gateway 原生支持接口限流该怎么玩
  9. mybatis 返回 插入的主键
  10. 【机房收费系统】多么痛的领悟