zoukankan      html  css  js  c++  java
  • 虹软开发心得---多线程实战开发避坑分享(C#)

    前言

    多线程一直是后端开发一个回避不了的话题。凡是大型项目,对高并发要求一般不低。除去利用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,对多线程方案有更好的建议,欢迎留言。

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

  • 相关阅读:
    184. Department Highest Salary【leetcode】sql,join on
    181. Employees Earning More Than Their Managers【leetcode】,sql,inner join ,where
    178. Rank Scores【leetcode】,sql
    177. Nth Highest Salary【leetcode】,第n高数值,sql,limit,offset
    176. Second Highest Salary【取表中第二高的值】,sql,limit,offset
    118. Pascal's Triangle【LeetCode】,java,算法,杨辉三角
    204. Count Primes【leetcode】java,算法,质数
    202. Happy Number【leetcode】java,hashSet,算法
    41. First Missing Positive【leetcode】寻找第一个丢失的整数,java,算法
    删除
  • 原文地址:https://www.cnblogs.com/ccLqqy/p/14475443.html
Copyright © 2011-2022 走看看