• 虹软开发心得---多线程实战开发避坑分享(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,对多线程方案有更好的建议,欢迎留言。

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

  • 相关阅读:
    开启和禁用Wifi热点命令
    C# IE代理操作
    Asp.net QueryString批量插入和更新
    Asp.net 插入或更改查询字符串
    C#如何判断线程池中所有的线程是否已经完成之Demo
    mysql 安装及设置密码
    c# iis回收应用程序池
    判断是否为移动端
    rtmp,m3u8 直播地址
    byte数组转换为字符串
  • 原文地址:https://www.cnblogs.com/ccLqqy/p/14475443.html
Copyright © 2020-2023  润新知