前言

多线程一直是后端开发一个回避不了的话题。凡是大型项目,对高并发要求一般不低。除去利用docker,k8s等框架进行负载均衡,动态扩容之外,多线程也是增强程序/系统并行处理能力的有效手段。恰巧虹软人脸识别用户中也有很多小伙伴经常为多线程的编程烦恼,今天笔者结合自己的开发项目,记录一下自己使用虹软算法包期间的踩坑爬坑经历,希望能给.net使用者一些避坑提示。同时声明,本文基于虹软人脸识别SDK 3.0 Windows C++ / X64版本(SDK下载链接:https://ai.arcsoft.com.cn/ucenter/resource/build/index.html#/addFreesdk/1002?from=index)作为讲解以及代码展示。

“坑”在哪里?

避坑识坑。使用虹软算法时,新手在多线程情况下,最容易出现2个问题。一是内存冲突(写保护内存错误),二是运行一段时间后,程序崩溃,监控后发现内存溢出现象。造成这种情景的原因是大多是目前的.Neter缺少C++编程基础,再者对C#的内存回收机制没有完全理解清晰。这也是很多开发者反应,自己在代码中明明使用了GC,为什么还会出现内存溢出,甚至对虹软SDK的内存回收提出质疑。笔者并非虹软员工,但因为工作原因,使用了人脸SDK1.2, 2.0, 2.1, 2.2, 3.0(3.1,4.0是企业版,没法测试,笔者是个人认证)以及人证SDK1.0,2.0这几个版本,并在部署上线前做过一定的压力测试。综合这2年半的运维情况,可以负责的告诉大家,虹软SDK的内存管理是可靠的,出现内存溢出,几乎肯定是使用者自身编程的问题。
由于虹软人脸SDK没有C#版本(希望后期出一个,毕竟有Java版,我们很不舒服),.Neter一直是封装C++版本使用。由于C++是直接操作内存(C#在safe模式下是线程安全语言,不直接操作内存),因此虹软引擎在初始化后,就将所需要的内存空间在内存中申请好了。引擎在被调用时,数据会写入到相关内存,如果多个线程调用同一个引擎执行相同操作,例如提取特征值,就将发生一个内存地址被同时修改的错误,即试图修改写保护内存。

避坑方案1:引擎捆绑法(一个引擎捆绑到一个线程)

.NET 4.0在线程方面加入了很多东西,其中就包括ThreadLocal类型,他的出现更大的简化了TLS的操作。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本,起到了线程隔离的作用。下面的代码,就是利用ThreadLocal确保多线程下,不同引擎安全运行。

static void Main()
{var local = new ThreadLocal<IntPtr>();//修改TLS的线程Thread th = new Thread(() =>{local.Value = intptr; //虹软引擎指针DoSomething();       //虹软人脸对比具体流程})th.Start();th.Join();
}

避坑方案2:引擎池法

.Net 4.0不仅引入了ThreadLocal,也引入了ConcurrentQueue。ConcurrentQueue队列是一个高效的线程安全的队列,ConcurrentQueue数据结构的示意图:


由于ConcurrentQueue是线程安全队列,我们不妨将引擎指针IntPtr变量放在里面,避免多线程情景被同时取用,用不需要手动在加锁,岂不美哉!思路如下(源码:https://github.com/18628271760/MultipleFacesProcess)

定义接口

public interface IEnginePoor
{public ConcurrentQueue<Intptr> FaceEnginePoor{get;set; }public IntPtr GetEngine(ConcurrentQueue<Intptr> queue);public void PutEngine(ConcurrentQueue<Intptr> queue,IntPtr item);
}

实际使用

public override async Task RecongnizationByFace(IAsyncStreamReader requestStream,IServerStreamWriter responseStream, ServerCallContext context){var faceQueue=new Queue();IntPtr featurePoint=IntPtr.Zero;IntPtr engine=FaceProcess.GetEngine(FaceProcess.FaceEnginePoor);FaceReply faceReply=new FaceReply();while(await requestStream.MoveNext()){//识别业务byte[] featureByte=requestStream.Current.FaceFeature.ToByteArray();if(featureByte.Length!=1032) //注意,2.x和3.x版本的人脸特征长度是1032.{continue;}featurePoint=Arcsoft_Face_Action.PutFeatureByteIntoFeatureIntPtr(featureByte);float maxScore=0f;while(engine==IntPtr.Zero){Task.Delay(10).Wait();engine=FaceProcess.GetEngine(FaceProcess.IDEnginePoor);}foreach(var f in StaticDataForTestUse.dbFaceInfor){float result=0;int compareStatus=Arcsoft_Face_3_0.ASFFaceFeatureCompare(engine, featurePoint, f.Key,ref result,1);if(compareStatus==0){if(result>=maxScore){maxScore=result;}if(result>=_faceMix&&result>=maxScore){faceReply.PersonName=f.Value;faceReply.ConfidenceLevel=result;}}else{faceReply.PersonName=$"对比异常 error code={compareStatus}";faceReply.ConfidenceLevel=result;}}if(maxScore<_faceMix){faceReply.PersonName=$"未找到匹配者";faceReply.ConfidenceLevel=maxScore;}Marshal.FreeHGlobal(featurePoint);await responseStream.WriteAsync(faceReply);}FaceProcess.PutEngine(FaceProcess.FaceEnginePoor,engine);}

除了线程调用引起的内存冲突外,引擎数量过多引起的内存溢出也是多线程情况下的一大痛点。引擎过少处理效率不足,引擎多了,出现程序崩溃。引擎数量如何规划?

避坑方案3:合理规划线程数量(引擎并发数量)

虹软的文档中友善的提示了大家,引擎的数量不超过系统内核数量(当然,我认为这建议比较保守,毕竟不同代数,不同档次的CPU的性能差别很大)。 对于初始化引擎数量,笔者根据自己的工程实践,做了以下总结供大家参考:

CPU方面:引擎数量根据系统CPU消耗情况估算,多个引擎同时处理时,CPU占有率不超过90%。
内存方面:每个引擎的内存消耗按400M估算(以3代SDK为例,其他版本可自行测试估算),系统内存占用不超过80%(注意:需要规划预留图片处理是需要的内存!)。
引擎数量取 CPU,内存限制数量中的最小值。
容器化部署尽量避免一个程序一个引擎(浪费注册码,增加资源消耗),但建议部署2,3个多引擎容器组成集群,并对每个容器做内存资源CPU限制,平衡稳定性,限制动态扩展容器数量。

总结

以上几点避坑建议是笔者从工程实践中得到的一点思考,欢迎更多的小伙伴尝试使用虹软SDK,对多线程方案有更好的建议,欢迎留言。

了解更多人脸识别产品相关内容请到虹软视觉开放平台哦

虹软开发心得---多线程实战开发避坑分享(C#)相关推荐

  1. python搭建项目结构_Django搭建项目实战与避坑细节详解

    Django 开发项目是很快的,有多快?看完本篇文章,你就知道了. 安装 Django 前提条件:已安装 Python. Django 使用 pip 命令直接就可以安装: pip install dj ...

  2. python避坑_Django搭建项目实战与避坑细节详解

    Django 开发项目是很快的,有多快?看完本篇文章,你就知道了. 安装 Django 前提条件:已安装 Python. Django 使用 pip 命令直接就可以安装: pip install dj ...

  3. 【创科之龙】零基础学习嵌入式开发以及项目实战开发【第二期视频】

    [创科之龙]零基础学习嵌入式开发以及项目实战开发[学习交流零基础火热进行ing] 大家好,我是aiku,上期的项目学习资料在电子发烧友论坛上分享,大家觉得都很好. 在这里我首先要感谢电子发烧友给我们的 ...

  4. 【鸿蒙开发】hpm-cli的安装避坑、详细使用教程

    [鸿蒙开发]hpm-cli的介绍.安装避坑.详细使用教程 文章目录 [鸿蒙开发]hpm-cli的介绍.安装避坑.详细使用教程 安装环境准备 使用教程 踩坑集合 HPM包管理器是华为鸿蒙推出的Harmo ...

  5. 新房装修材料避坑分享

    第一次装修怕被坑,雅静说选哪些品牌的材料才能避坑 1,水管选伟星,日丰,联塑,因为水管的核心是质保, 要选水管管件和人工焊接点,双重质保的 2,电线选正泰,德力西,公牛,熊猫,一定要选国际带有3C认证 ...

  6. java 电商 插件 开发_JAVA项目实战开发电商项目案例(六与七)商品分类与商品模块管理开发...

    购物网站中,商品管理板块也是重要的一个版块,下面我会从后台管理系统和前台管理页面去讲述购物网站商品模块功能的开发. 1演示效果 1.1前台演示地址演示地址 1.2后台演示地址演示地址 2商品分类功能 ...

  7. IT老齐架构300讲笔记(047) 避坑分享财Z部金财平台用主键用了UUID后出现的问题

    目录 一.场景 二.UUID的版本 2.1 基于时间的UUID 2.2 DCE安全的UUID 2.3 基于命名空间的UUID(MD5.SH1) 2.4 基于随机数的UUID(最常用) 三.为什么 UU ...

  8. 【Rust开荒】数组避坑分享

    ​ 正常语言的数组切片: a = [1,2,3] print(a) 但是实际上讲,如果a为空时,在Python中,其返回为空. Rust中也可以使用,但是!先决条件是你的数组得到了初始化: let m ...

  9. 零基础学习嵌入式入门以及项目实战开发【手把手教+国内独家+原创】

    零基础学习嵌入式入门以及项目实战开发[手把手教+国内独家+原创] 独家拥有,绝对经典                            创 科 之 龙 嵌入式开发经典系列教程 [第一期] 主讲人: ...

最新文章

  1. mysql设计积分兑换表_积分系统数据库表设计.docx
  2. 由于 Web 服务器上的“ISAPI 和 CGI 限制”列表设置,无法提供您请求的页面。
  3. [leetcode]94.二叉树的中序遍历
  4. java 手机网站 cookie操作_java操作cookie问题
  5. ESXI忘记密码怎么办?
  6. 基于FPGA的车牌识别系统
  7. 《Go学习笔记 . 雨痕》方法
  8. [译]1-Key-Value Coding Programming Guide 官方文档第一部分
  9. pandas数据合并与重塑_PANDAS 数据合并与重塑(concat篇)
  10. 选择mysql开发的原因_MySQL开发技巧
  11. 计算机打不开硬盘,电脑有一个磁盘打不开怎么处理
  12. hadoop中使用lzo压缩算法
  13. matlab .m 返回值,MATLAB一个M文件的function返回值怎么在另一个M文件中的函数调用这个返回值?...
  14. 基于STM32的实时操作系统FreeRTOS移植教程(手动移植)
  15. 服务器开虚拟机总是gpu满载,vSphere 环境机器学习 GPU 加速方案选型
  16. 【HTML 教程系列第 19 篇】HTML 表格中的行合并与列合并
  17. MySQL忘记密码修改密码
  18. 非侵入式负荷分解之BLUED数据集
  19. Invalid character (CR or LF) found in method name
  20. 深入理解java虚拟机 新生代_深入理解java虚拟机:笔记

热门文章

  1. 用计算机模拟双缝干涉实验报告,杨氏双缝干涉实验的计算机模拟
  2. [Jetson Nano]SSH连接Jetson Nano时出现Xlib: extension NV-GLX missing on display localhost:10.0
  3. 打造自己的装机U盘(一)
  4. POJ1007:DNA排序
  5. 在线录制视频声音,纯前端实现,无需上传和下载
  6. matlab学习(三)三维曲线和曲面
  7. 经典Bug永流传---每周一“虫”(二十二)
  8. ASO优化:关于后台关键词设置的逗号的使用
  9. 基于Linux的软件定时器(线程安全)
  10. 万向节锁与四元数旋转