Wlkr.Core.ThreadUtils

项目背景

早在PaddleOCR 2.2版本时期,认识了周杰大佬的PaddleSharp项目,试用其中PaddleOCR时,发现它在改为web api调用时会报错,大概意思是OCR实例的内存只能由其创建的线程才具有访问权限,于是就有了本项目的雏形。

潜伏于大佬Q群中很长时间,这个问题更是老生常谈。虽然后来大佬实现了基于BlockingCollection的线程安全示例,不过估计因为README全是英文,还是出现了很多星际玩家。

食用方式

项目中的SafeThreadRunner,为了实现更直观的调用方式(var res = ocr.run(mat)),使用了3个信号量SemaphoreSlim实现了线程安全的轮询方法,它们的作用分别是否空闲,唤醒线程,返回结果。

SafeThreadRunner<Cls, In, Out>,可以从泛型的名字猜测,Cls对应OCR的实例(如All、Rec、Det等任务),In为输入即Mat,Out为输出即Restful规范的返回结果RestResult<Out>

核心代码

代码很简单,下面这段代码,通过信号量负责检查线程是否空闲。如果空闲, 则设置入参,唤醒线程。

public RestResult<Out> Run(In src)
{
//是否空闲
safeSrcSlim.Wait();
//设置Source
Source = src;
//恢复线程,运行runFunc
safeRunSlim.Release();
//等待runFunc结果
safeResSlim.Wait();
//释放信号量,设为空闲
safeSrcSlim.Release();
return Result;
}

唤醒后则执行识别,告诉调用者识别完成,输出结果。(Dispose同理)

private void RunByThread()
{
using Cls cls = initFunc();
while (true)
{
safeRunSlim.Wait();
if (IsDisposed)
return;
try
{
Result = runFunc(cls, Source);
}
catch (Exception ex)
{
Result = new RestResult<Out>()
{
code = "500",
msg = ex.Message
};
}
finally
{
safeResSlim.Release();
}
}
}

nuget安装参考命令

# 新建一个console项目
dotnet new console
# 添加nuget包
dotnet add package Wlkr.SafePaddleOCR

CPU加速示例

本项目实现的SafePaddleOCR为PaddleOcrAll开启Mkldnn的实例,使用方式如下:

//Warmup
SafePaddleOCR safePaddleOCR = new SafePaddleOCR();
string imgPath = @"../../../../vx_images/DimTechStudio-Logo.png";
var res = safePaddleOCR.Run(imgPath);
Console.Write(@"res: {res.data.Text}");

定制示例

如需要定制自己的线程安全实例,可参考:

//实例的初始化方法
Func<PaddleOcrAll> initFuc = () =>
{
Action<PaddleConfig> device = PaddleDevice.Mkldnn();
var poa = new PaddleOcrAll(LocalFullModels.ChineseV3, device)
{
Enable180Classification = true,
AllowRotateDetection = true,
};
return poa;
};
//实例的执行方法
Func<PaddleOcrAll, Mat, RestResult<PaddleOcrResult>> mthdFunc = (cls, source) =>
{
var res = cls.Run(source);
return new RestResult<PaddleOcrResult>(res);
};
//声明
SafeThreadRunner<PaddleOcrAll, Mat, PaddleOcrResult> safeThreadRunner = new SafeThreadRunner<PaddleOcrAll, Mat, PaddleOcrResult>(OCRFactory.BuildAllWithMkldnn, OCRFactory.RunAll);
//运行
string imgPath = @"../../../../vx_images/DimTechStudio-Logo.png";
using var mat = Cv2.ImRead(filePath, ImreadModes.AnyColor);
var res = safeThreadRunner.Run(mat);

SemaphoreSlimBlockingCollection对比

  • 单实例测试:性能几乎一样,没有明显差异
  • 多实例测试:报错!!!

测试用的机器是笔记本 CPU R7 5800H,内存32G。

两种方式均会报错,实例数越多,报错概率越高,错误提示依然内存错误的问题。

System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

另外SemaphoreSlim的方式比BlockingCollection的方式出现的更频繁,尤其在4实例时基本无法完成10240次OCR测试。

两者在出现代码位置也不经相同,Det、Cls、Rec三种模型预测时均可能错误。

  • 周杰大佬的项目优势:实现生产者消费者模式
  • 本项目优势:单实例Dispose

由于我也重构过周杰大佬的QueuedPaddleOcrAll.cs,其Dispose方式只能释放所有实例。虽然我增加了动态添加/删除实例的功能,但其使用了Task作为轮询的载体,Task不能像Thread那样有真正意义上的取消动作。CancellationToken实现的取消最大缺陷是在阻塞时是无效的。即便我实现了取消,它也必须从blockingCollection.GetConsumingEnumerable()获取到消息执行一次OCR识别,才能释放OCR实例,极端情况下等于无法是释放。

而本项目使用了SemaphoreSlim,执行Dispose时只要线程是空闲即可触发OCR实例的释放。

测试数据

  • 硬件配置: CPU R7 5800H(8核16线程主频3.2GHz),内存32G
  • 测试图片:10张,数字0000~0009,宽高160*80,类型png
  • 风扇转速常开最大,排除CPU温度影响
  • OCR实例参数:All,CPU,Mkldnn

由于10240次大概率报错,无法完成测试,这里改为256次。

平均毫秒 平均毫秒 平均毫秒
OCR次数 256 256 256
实例数 1 2 4
SemaphoreSlim 27.36328125 17.640625 12.6328125
BlockingCollection 27.74609375 17.8984375 12.640625
  • 思路转换

从单进程4实例,改为4进程1实例测试,测试了1次没有报错,每个进程10240次,平均50ms。

  • 测试缺陷:

    • 居然没用web api来测试而是用了console测试,与项目背景背道而驰……
    • 由于用的图片较少,没法作为内存压力测试的参考

总结

基于思路转换的测试,虽然测试次数少了点,不过目前来看,当作为web api时,1进程1实例,多开进程,利用负载均衡来提高并发和服务器利用率,为最优方案。

Author Info

DimWalker

2023 广州市增城区黯影信息科技部

https://www.dimtechstudio.com/

利用信号量SemaphoreSlim实现PaddleOCR的线程安全访问的更多相关文章

  1. 在linux下利用信号量实现一个写者线程多个读者线程

    #include<pthread.h> #include<string.h> #include<stdlib.h> #include<stdio.h> ...

  2. 进程间通信机制(管道、信号、共享内存/信号量/消息队列)、线程间通信机制(互斥锁、条件变量、posix匿名信号量)

    注:本分类下文章大多整理自<深入分析linux内核源代码>一书,另有参考其他一些资料如<linux内核完全剖析>.<linux c 编程一站式学习>等,只是为了更好 ...

  3. 并发编程(五)--GIL、死锁现象与递归锁、信号量、Event事件、线程queue

    一.GIL全局解释器锁 1.什么是全局解释器锁 GIL本质就是一把互斥锁,相当于执行权限,每个进程内都会存在一把GIL,同一进程内的多个线程,必须抢到GIL之后才能使用Cpython解释器来执行自己的 ...

  4. GIL全局解释锁,死锁,信号量,event事件,线程queue,TCP服务端实现并发

    一.GIL全局解释锁 在Cpython解释器才有GIL的概念,不是python的特点 在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势. 1.GIL介绍 ...

  5. 并发编程(五)——GIL全局解释器锁、死锁现象与递归锁、信号量、Event事件、线程queue

    GIL.死锁现象与递归锁.信号量.Event事件.线程queue 一.GIL全局解释器锁 1.什么是全局解释器锁 GIL本质就是一把互斥锁,相当于执行权限,每个进程内都会存在一把GIL,同一进程内的多 ...

  6. TCP协议下的服务端并发,GIL全局解释器锁,死锁,信号量,event事件,线程q

    TCP协议下的服务端并发,GIL全局解释器锁,死锁,信号量,event事件,线程q 一.TCP协议下的服务端并发 ''' 将不同的功能尽量拆分成不同的函数,拆分出来的功能可以被多个地方使用 TCP服务 ...

  7. 企业应用架构研究系列二十六:信号量SemaphoreSlim与Semaphore

    在进行多线程程序的开发和设计的过程中,不可避免的需要引入semaphore信号量这个组件,这是.net框架提供的一个对多线程计数互斥的方案,就是允许指定的线程个数访问特定的资源而增加的 一个" ...

  8. C#多线程编程のSemaphore(信号量,负责协调各个线程)

    Semaphore负责协调线程,可以限制对某一资源访问的线程数量 这里对SemaphoreSlim类的用法做一个简单的例子: namespace WpfApplication6 { /// <s ...

  9. InvokeRequired 线程间访问

    zt: http://www.x2blog.cn/jinhong618/?tid=22389 问: f (this.InvokeRequired)            {               ...

  10. WPF [调用线程无法访问此对象,因为另一个线程拥有该对象。] 解决方案以及如何实现字体颜色的渐变

    本文说明WPF [调用线程无法访问此对象,因为另一个线程拥有该对象.] 解决方案以及如何实现字体颜色的渐变 先来看看C#中Timer的简单说明,你想必猜到实现需要用到Timer的相关知识了吧. C# ...

随机推荐

  1. C++面试八股文:知道std::unordered_set/std::unordered_map吗?

    某日二师兄参加XXX科技公司的C++工程师开发岗位第27面: 面试官:知道std::unordered_set/std::unordered_map吗? 二师兄:知道.两者都是C++11引入的新容器, ...

  2. Kafka中的消费者Offset

    消费者位移 每个 consumer 实例都会为它消费的分区维护属于自己的位置信息来记录当前消费了多少条消息.这在 Kafka 中有一个特有的术语:位移(offset). 相比较将offset保存在服务 ...

  3. 多个commit合并为一个

    在进行多个commit合并成一个博客编写的过程中,你可以使用以下代码示例作为参考: # 合并多个commit git rebase -i HEAD~N # N代表需要合并的commit数目,例如合并最 ...

  4. 华为云GaussDB亮相2023可信数据库发展大会,荣获三项评测证书!

    摘要:2023可信数据库发展大会上,华为云数据库服务产品部总经理苏光牛围绕华为云GaussDB的产品能力和实践进行了分享 本文分享自华为云社区<华为云GaussDB亮相2023可信数据库发展大会 ...

  5. js中数组的方法,32种方法

    数组的32中方法=>{ 1.push(): 在数组末尾添加一个或多个元素,并返回修改后的数组. let fruits = ['apple', 'banana', 'orange']; fruit ...

  6. 如何根据oops函数偏移快速定位源码?

    如何根据函数偏移快速定位源码? 在内核栈的输出中,你一定注意到每一个函数的输出格式都是函数名+偏移量,而这儿的偏移就是调用下一个函数的位置.那么,能不能根据函数名+偏移量直接定位源码的位置呢? 答案是 ...

  7. 【Oracle】 instr函数与substr函数以及自制分割函数

    Oracle instr函数与substr函数以及自制分割函数 instr通常被用来作为判断某个字符串中是否含有执行字符串和将返回结果作为一些数据分割的数据,即有模糊查询like的作用,当返回的查询结 ...

  8. 云原生可观测框架 OpenTelemetry 基础知识(架构/分布式追踪/指标/日志/采样/收集器)

    什么是 OpenTelemetry? OpenTelemetry 是一个开源的可观测性框架,由云原生基金会(CNCF)托管.它是 OpenCensus 和 OpenTracing 项目的合并.旨在为所 ...

  9. Nginx反向代理服务流式输出设置

    Nginx反向代理服务流式输出设置 1.问题场景 提问:为什么我部署的服务没有流式响应 最近在重构原有的GPT项目时,遇到gpt回答速度很慢的现象.在使用流式输出的接口时,接口响应速度居然还是达到了3 ...

  10. quarkus数据库篇之四:本地缓存

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<quarkus数据库篇> ...