? ? ? ?虹軟開放人臉識別SDK以來放仗,成功把人臉識別技術(shù)拉下神臺抑党,幾乎所有開發(fā)者可以“0成本”使用到人臉(證)到其項目包警。但在官方論壇,QQ群底靠,微信群等平臺害晦,很多初學(xué)者對如何在多線程下使用產(chǎn)生疑惑,掉入坑中(尤其是沒有C++的基礎(chǔ)的C#開發(fā))暑中。今天壹瘟,分享兩種.net (core)下的多線程使用方式,貢大家探討鳄逾。大家有更好的方式稻轨,也可以積極留言交流。
? ? ? ?先分析問題來源雕凹,為什么C#的一般多線程調(diào)用方式容易產(chǎn)生錯誤殴俱,尤其是“嘗試寫保護(hù)內(nèi)存”的錯誤。原因是C#開發(fā)使用的虹軟的算法SDK均為C++版本(Windows/Linux)枚抵,C++作為線程不安全的程序粱挡,可以直接操作內(nèi)存。多個線程同時調(diào)用一個引擎俄精,就是同時對一段內(nèi)存操作,產(chǎn)生內(nèi)存錯誤榕堰,程序崩潰竖慧。
解決方案一:基于ThreadLocal?強制一個線程捆綁一個引擎。
? ? ? ?ThreadLocal的主要作用是讓各個線程維持自己的變量逆屡。ThreadLocal 是線程的局部變量圾旨, 是每一個線程所單獨持有的,其他線程不能對其進(jìn)行訪問魏蔗,?通常是類中的 private static 字段砍的。當(dāng)使用ThreadLocal維護(hù)變量的時候 為每一個使用該變量的線程提供一個獨立的變量副本,即每個線程內(nèi)部都會有一個該變量莺治,這樣同時多個線程訪問該變量并不會彼此相互影響廓鞠,因此他們使用的都是自己從內(nèi)存中拷貝過來的變量的副本, 這樣就不存在線程安全問題谣旁,也不會影響程序的執(zhí)行性能床佳。在虹軟人臉的具體應(yīng)用中,毫無疑問榄审,把初始化好的引擎指針(C#中的Intptr類型)賦值給線程的Threadlocal砌们,就可以開心的玩耍了。找個網(wǎng)上的code演示下:(本人基于ThreadLocal 的工程找不到了)
static void Main()
{
var local = new ThreadLocal<IntPtr>();
//修改TLS的線程
Thread th = new Thread(() =>
{
local.Value = intptr; //虹軟引擎指針
DoSomething();? ? ? ?//虹軟人臉對比具體流程
})
th.Start();
th.Join();
}
解決方案二:基于“引擎池”實現(xiàn)多線程與高并發(fā)。
相比于方案一浪感,我更喜歡“引擎池”昔头,應(yīng)為它更方便靈活,還更適合.net core Web Api這樣的后端框架影兽。廢話不說揭斧,代碼伺候:
1. 定義"引擎池"接口 (由于我方業(yè)務(wù)需要,初始化了3個不同的引擎池赢笨,相關(guān)的引擎參數(shù)不相同)
public interface IEnginePoor
? ? {
? ? ? ? public ConcurrentQueue<IntPtr> FaceEnginePoor { get; set; }
? ? ? ? public ConcurrentQueue<IntPtr> IDEnginePoor { get; set; }
? ? ? ? public ConcurrentQueue<IntPtr> AIEnginePoor { get; set; }
? ? ? ? public IntPtr GetEngine(ConcurrentQueue<IntPtr> queue);
? ? ? ? public void PutEngine(ConcurrentQueue<IntPtr> queue, IntPtr item);
? ? }
2. 實現(xiàn)相關(guān)接口 (Arcsoft_Face_3_0 為虹軟dll的C#封裝)
public class Arcsoft_Face_Action : Arcsoft_Face_3_0, IEnginePoor
? ? {
? ? ? ? public string AppID { get; }
? ? ? ? public string AppKey { get; }
? ? ? ? public int FaceEngineNums { get; set; }
? ? ? ? public int IDEngineNums { get; set; }
? ? ? ? public int AIEngineNums { get; set; }
? ? ? ? public ConcurrentQueue<IntPtr> FaceEnginePoor { get; set; }
? ? ? ? public ConcurrentQueue<IntPtr> IDEnginePoor { get; set; }
? ? ? ? public ConcurrentQueue<IntPtr> AIEnginePoor { get; set; }
private int InitEnginePool()
? ? ? ? {
? ? ? ? ? ? try
? ? ? ? ? ? {
? ? ? ? ? ? ? ? for (int index = 0; index < FaceEngineNums; index++)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? IntPtr enginePtr = IntPtr.Zero;
? ? ? ? ? ? ? ? ? ? Arcsoft_Face_Action faceAction = new Arcsoft_Face_Action(AppID, AppKey);
? ? ? ? ? ? ? ? ? ? enginePtr = faceAction.InitASFEnginePtr(ParmsBestPractice.faceBaseMask);
? ? ? ? ? ? ? ? ? ? PutEngine(FaceEnginePoor, enginePtr);
? ? ? ? ? ? ? ? ? ? Console.WriteLine($"FaceEnginePoor add {enginePtr}");
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? for (int index = 0; index < IDEngineNums; index++)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? IntPtr enginePtr = IntPtr.Zero;
? ? ? ? ? ? ? ? ? ? Arcsoft_Face_Action faceAction = new Arcsoft_Face_Action(AppID, AppKey);
? ? ? ? ? ? ? ? ? ? enginePtr = faceAction.InitASFEnginePtr(ParmsBestPractice.faceBaseMask);
? ? ? ? ? ? ? ? ? ? PutEngine(IDEnginePoor, enginePtr);
? ? ? ? ? ? ? ? ? ? Console.WriteLine($"IDEnginePoor add {enginePtr}");
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? for (int index = 0; index < AIEngineNums; index++)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? IntPtr enginePtr = IntPtr.Zero;
? ? ? ? ? ? ? ? ? ? int aiMask = FaceEngineMask.ASF_AGE | FaceEngineMask.ASF_GENDER | FaceEngineMask.ASF_FACE3DANGLE | FaceEngineMask.ASF_LIVENESS;
? ? ? ? ? ? ? ? ? ? Arcsoft_Face_Action faceAction = new Arcsoft_Face_Action(AppID, AppKey);
? ? ? ? ? ? ? ? ? ? enginePtr = faceAction.InitASFEnginePtr(ParmsBestPractice.faceBaseMask | aiMask);
? ? ? ? ? ? ? ? ? ? PutEngine(AIEnginePoor, enginePtr);
? ? ? ? ? ? ? ? ? ? Console.WriteLine($"AIEnginePoor add {enginePtr}");
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? return 0;
? ? ? ? ? ? }
? ? ? ? ? ? catch (Exception ex)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? throw new Exception($"InitEnginePool--> exception {ex}");
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? public IntPtr GetEngine(ConcurrentQueue<IntPtr> queue)
? ? ? ? {
? ? ? ? ? ? IntPtr item = IntPtr.Zero;
? ? ? ? ? ? if (queue.TryDequeue(out item))
? ? ? ? ? ? {
? ? ? ? ? ? ? ? return item;
? ? ? ? ? ? }
? ? ? ? ? ? else
? ? ? ? ? ? {
? ? ? ? ? ? ? ? return IntPtr.Zero;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? public void PutEngine(ConcurrentQueue<IntPtr> queue, IntPtr item)
? ? ? ? {
? ? ? ? ? ? if (item != IntPtr.Zero)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? queue.Enqueue(item);
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? public void Arcsoft_EnginePool(int faceEngineNums , int idEngineNums , int aiEngineNums)
? ? ? ? {
? ? ? ? ? ? FaceEnginePoor = new ConcurrentQueue<IntPtr>();
? ? ? ? ? ? IDEnginePoor = new ConcurrentQueue<IntPtr>();
? ? ? ? ? ? AIEnginePoor = new ConcurrentQueue<IntPtr>();
? ? ? ? ? ? try
? ? ? ? ? ? {
? ? ? ? ? ? ? ? FaceEngineNums = faceEngineNums;
? ? ? ? ? ? ? ? IDEngineNums = idEngineNums;
? ? ? ? ? ? ? ? AIEngineNums = aiEngineNums;
? ? ? ? ? ? ? ? int status = InitEnginePool();
? ? ? ? ? ? ? ? if (status != 0)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? throw new Exception("引擎池初始化失斘打颉!");
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? catch (Exception ex)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? throw new Exception($"ArcSoft_EnginePool-->ArcSoft_EnginePool exception as: {ex}");
? ? ? ? ? ? }
? ? ? ? }
private int InitEnginePool()
? ? ? ? {
? ? ? ? ? ? try
? ? ? ? ? ? {
? ? ? ? ? ? ? ? for (int index = 0; index < FaceEngineNums; index++)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? IntPtr enginePtr = IntPtr.Zero;
? ? ? ? ? ? ? ? ? ? Arcsoft_Face_Action faceAction = new Arcsoft_Face_Action(AppID, AppKey);
? ? ? ? ? ? ? ? ? ? enginePtr = faceAction.InitASFEnginePtr(ParmsBestPractice.faceBaseMask);
? ? ? ? ? ? ? ? ? ? PutEngine(FaceEnginePoor, enginePtr);
? ? ? ? ? ? ? ? ? ? Console.WriteLine($"FaceEnginePoor add {enginePtr}");
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? for (int index = 0; index < IDEngineNums; index++)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? IntPtr enginePtr = IntPtr.Zero;
? ? ? ? ? ? ? ? ? ? Arcsoft_Face_Action faceAction = new Arcsoft_Face_Action(AppID, AppKey);
? ? ? ? ? ? ? ? ? ? enginePtr = faceAction.InitASFEnginePtr(ParmsBestPractice.faceBaseMask);
? ? ? ? ? ? ? ? ? ? PutEngine(IDEnginePoor, enginePtr);
? ? ? ? ? ? ? ? ? ? Console.WriteLine($"IDEnginePoor add {enginePtr}");
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? for (int index = 0; index < AIEngineNums; index++)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? IntPtr enginePtr = IntPtr.Zero;
? ? ? ? ? ? ? ? ? ? int aiMask = FaceEngineMask.ASF_AGE | FaceEngineMask.ASF_GENDER | FaceEngineMask.ASF_FACE3DANGLE | FaceEngineMask.ASF_LIVENESS;
? ? ? ? ? ? ? ? ? ? Arcsoft_Face_Action faceAction = new Arcsoft_Face_Action(AppID, AppKey);
? ? ? ? ? ? ? ? ? ? enginePtr = faceAction.InitASFEnginePtr(ParmsBestPractice.faceBaseMask | aiMask);
? ? ? ? ? ? ? ? ? ? PutEngine(AIEnginePoor, enginePtr);
? ? ? ? ? ? ? ? ? ? Console.WriteLine($"AIEnginePoor add {enginePtr}");
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? return 0;
? ? ? ? ? ? }
? ? ? ? ? ? catch (Exception ex)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? throw new Exception($"InitEnginePool--> exception {ex}");
? ? ? ? ? ? }
? ? ? ? }
}
3. 實現(xiàn)CustomServiceCollection 方便依賴注入
public static class CustomServiceCollection
? ? {
? ? ? ? public static IServiceCollection AddArcSoftFaceService(this IServiceCollection services, Arcsoft_Face_Action enginePool)
? ? ? ? {
? ? ? ? ? ? services.AddSingleton<IEnginePoor, Arcsoft_Face_Action>(x => enginePool);
? ? ? ? ? ? return services;
? ? ? ? }
? ? }
4. 在Startup里面添加“虹軟”Service茧妒。(同時推薦搭配Microsoft.AspNetCore.ConcurrencyLimiter中間件萧吠,限制并發(fā)量,以免內(nèi)存不足)
public void ConfigureServices(IServiceCollection services)
? ? ? ? {
? ? ? ? ? ? services.AddMvc();
? ? ? ? ? ? services.AddControllers();
? ? ? ? ? ? //用于傳入的請求進(jìn)行排隊處理,避免線程池的不足.
? ? ? ? ? ? services.AddQueuePolicy(options =>
? ? ? ? ? ? {
? ? ? ? ? ? ? ? //最大并發(fā)請求數(shù) (建議與引擎數(shù)保持一直桐筏,虹軟官方的說法是的最大引擎數(shù)不超過電腦的核數(shù)纸型,我反正是不信的,難道志強和奔騰一樣梅忌?內(nèi)存足夠大狰腌,我一般是和虛擬線程數(shù)一致,比如6核12線程牧氮,我就開12個引擎琼腔。)
? ? ? ? ? ? ? ? options.MaxConcurrentRequests = faceEngineNums;
? ? ? ? ? ? ? ? //請求隊列長度限制
? ? ? ? ? ? ? ? options.RequestQueueLimit = requestQueueLimit;
? ? ? ? ? ? });
? ? ? ? ? ? //添加虹軟“引擎池”服務(wù)
? ? ? ? ? ? Arcsoft_Face_Action enginePool = new Arcsoft_Face_Action(appID, faceKey);
? ? ? ? ? ? enginePool.Arcsoft_EnginePool(faceEngineNums, 0, 0);
? ? ? ? ? ? services.AddArcSoftFaceService(enginePool);
? ? ? ? }
5.??在Controller里面實際使用。
public class FaceController : ControllerBase
{
public FaceController(IConfiguration configuration, IEnginePoor process)
? ? ? ? {
? ? ? ? ? ? Configuration = configuration;
? ? ? ? ? ? FaceProcess = process;
? ? ? ? ? ? float.TryParse(Configuration.GetSection("AppSettings:FaceMixLevel").Value, out faceMix);
? ? ? ? ? ? int.TryParse(Configuration.GetSection("AppSettings:MaxProcessTime").Value, out maxProcessTime);
? ? ? ? }
[HttpPost]
? ? ? ? [Route("CompareTwoFaces")]
? ? ? ? [DisableRequestSizeLimit]
? ? ? ? public IActionResult CompareTwoFaces(IFormFile faceA, IFormFile faceB)
? ? ? ? {
? ? ? ? ? ? IntPtr engine = FaceProcess.GetEngine(FaceProcess.FaceEnginePoor);
? ? ? ? ? ? CancellationTokenSource tokenSource = new CancellationTokenSource();
? ? ? ? ? ? CustomResult faceResult = new CustomResult();
? ? ? ? ? ? Tuple<bool, IntPtr, string> faceAResult = new Tuple<bool, IntPtr, string>(false, IntPtr.Zero, null);
? ? ? ? ? ? Tuple<bool, IntPtr, string> faceBResult = new Tuple<bool, IntPtr, string>(false, IntPtr.Zero, null);
? ? ? ? ? ? //調(diào)用引擎池邏輯踱葛!
? ? ? ? ? ? var task = Task.Run(() =>
? ? ? ? ? ? {
? ? ? ? ? ? ? ? while (engine == IntPtr.Zero)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? Task.Delay(10);
? ? ? ? ? ? ? ? ? ? if (tokenSource.Token.IsCancellationRequested)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? throw new Exception("等待引擎超時丹莲!");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? engine = FaceProcess.GetEngine(FaceProcess.FaceEnginePoor);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? using (var ms = new MemoryStream())
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? faceA.CopyTo(ms);
? ? ? ? ? ? ? ? ? ? faceAResult = Arcsoft_Face_Action.TryExtractSingleFaceFeature(ms, 10, engine);
? ? ? ? ? ? ? ? ? ? if (!faceAResult.Item1)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? faceResult.Success = false;
? ? ? ? ? ? ? ? ? ? ? ? faceResult.msg = faceAResult.Item3;
? ? ? ? ? ? ? ? ? ? ? ? return;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? using (var ms = new MemoryStream())
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? faceB.CopyTo(ms);
? ? ? ? ? ? ? ? ? ? faceBResult = Arcsoft_Face_Action.TryExtractSingleFaceFeature(ms, 10, engine);
? ? ? ? ? ? ? ? ? ? if (!faceBResult.Item1)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? faceResult.Success = false;
? ? ? ? ? ? ? ? ? ? ? ? faceResult.msg = faceBResult.Item3;
? ? ? ? ? ? ? ? ? ? ? ? return;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? float result = 0;
? ? ? ? ? ? ? ? int compareStatus = Arcsoft_Face_3_0.ASFFaceFeatureCompare(engine, faceAResult.Item2, faceBResult.Item2, ref result, ASF_CompareModel.ASF_LIFE_PHOTO);
? ? ? ? ? ? ? ? if (compareStatus == 0)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? faceResult.Success = true;
? ? ? ? ? ? ? ? ? ? faceResult.msg = $"相似度: {result} 接客引擎:{engine}";
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? faceResult.Success = false;
? ? ? ? ? ? ? ? ? ? faceResult.msg = $"compareStatus error code = {compareStatus} 接客引擎:{engine}";
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }, tokenSource.Token);
? ? ? ? ? ? //響應(yīng)時間控制
? ? ? ? ? ? try
? ? ? ? ? ? {
? ? ? ? ? ? ? ? int timeLast = maxProcessTime * 1000;
? ? ? ? ? ? ? ? while (timeLast > 0)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? Task.Delay(100).Wait();
? ? ? ? ? ? ? ? ? ? timeLast = timeLast - 100;
? ? ? ? ? ? ? ? ? ? if (task.IsCompletedSuccessfully)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? return Ok(JsonConvert.SerializeObject(faceResult));
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? tokenSource.Cancel();
? ? ? ? ? ? ? ? return Ok(JsonConvert.SerializeObject(faceResult));
? ? ? ? ? ? }
? ? ? ? ? ? catch (Exception ex)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? faceResult.Success = false;
? ? ? ? ? ? ? ? faceResult.msg = ex.Message;
? ? ? ? ? ? ? ? return Ok(JsonConvert.SerializeObject(faceResult));
? ? ? ? ? ? }
? ? ? ? ? ? finally
? ? ? ? ? ? {
? ? ? ? ? ? ? ? FaceProcess.PutEngine(FaceProcess.FaceEnginePoor, engine);
? ? ? ? ? ? ? ? Marshal.FreeHGlobal(faceAResult.Item2);
? ? ? ? ? ? ? ? Marshal.FreeHGlobal(faceBResult.Item2);
? ? ? ? ? ? ? ? tokenSource.Dispose();
? ? ? ? ? ? ? ? GC.Collect();
? ? ? ? ? ? }
? ? ? ? }
}
6.??結(jié)果演示:
后記:
? ? ? ?多線程一般是為應(yīng)對高并發(fā)的情形,本篇文章僅僅提供一種應(yīng)對虹軟人臉識別的多線程的簡單初級處理方式尸诽,但對于真正的互聯(lián)網(wǎng)級別的高并發(fā)甥材,肯定是力不從心的。筆者在其他高并發(fā)項目中性含,還是基于k8s的redis+Kafka的分布式微服務(wù)架構(gòu)處理洲赵,一個pod里面一個引擎(有點廢號)。當(dāng)然商蕴,Adp vNext也很香叠萍。對于私人開發(fā)者,每年100個注冊碼绪商,k8s的消耗可能太大俭令,本文的方式還是能節(jié)約一些注冊碼。