利用信号量SemaphoreSlim实现PaddleOCR的线程安全访问

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);
SemaphoreSlim与BlockingCollection对比
- 单实例测试:性能几乎一样,没有明显差异
- 多实例测试:报错!!!
测试用的机器是笔记本 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的线程安全访问的更多相关文章
- 在linux下利用信号量实现一个写者线程多个读者线程
#include<pthread.h> #include<string.h> #include<stdlib.h> #include<stdio.h> ...
- 进程间通信机制(管道、信号、共享内存/信号量/消息队列)、线程间通信机制(互斥锁、条件变量、posix匿名信号量)
注:本分类下文章大多整理自<深入分析linux内核源代码>一书,另有参考其他一些资料如<linux内核完全剖析>.<linux c 编程一站式学习>等,只是为了更好 ...
- 并发编程(五)--GIL、死锁现象与递归锁、信号量、Event事件、线程queue
一.GIL全局解释器锁 1.什么是全局解释器锁 GIL本质就是一把互斥锁,相当于执行权限,每个进程内都会存在一把GIL,同一进程内的多个线程,必须抢到GIL之后才能使用Cpython解释器来执行自己的 ...
- GIL全局解释锁,死锁,信号量,event事件,线程queue,TCP服务端实现并发
一.GIL全局解释锁 在Cpython解释器才有GIL的概念,不是python的特点 在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势. 1.GIL介绍 ...
- 并发编程(五)——GIL全局解释器锁、死锁现象与递归锁、信号量、Event事件、线程queue
GIL.死锁现象与递归锁.信号量.Event事件.线程queue 一.GIL全局解释器锁 1.什么是全局解释器锁 GIL本质就是一把互斥锁,相当于执行权限,每个进程内都会存在一把GIL,同一进程内的多 ...
- TCP协议下的服务端并发,GIL全局解释器锁,死锁,信号量,event事件,线程q
TCP协议下的服务端并发,GIL全局解释器锁,死锁,信号量,event事件,线程q 一.TCP协议下的服务端并发 ''' 将不同的功能尽量拆分成不同的函数,拆分出来的功能可以被多个地方使用 TCP服务 ...
- 企业应用架构研究系列二十六:信号量SemaphoreSlim与Semaphore
在进行多线程程序的开发和设计的过程中,不可避免的需要引入semaphore信号量这个组件,这是.net框架提供的一个对多线程计数互斥的方案,就是允许指定的线程个数访问特定的资源而增加的 一个" ...
- C#多线程编程のSemaphore(信号量,负责协调各个线程)
Semaphore负责协调线程,可以限制对某一资源访问的线程数量 这里对SemaphoreSlim类的用法做一个简单的例子: namespace WpfApplication6 { /// <s ...
- InvokeRequired 线程间访问
zt: http://www.x2blog.cn/jinhong618/?tid=22389 问: f (this.InvokeRequired) { ...
- WPF [调用线程无法访问此对象,因为另一个线程拥有该对象。] 解决方案以及如何实现字体颜色的渐变
本文说明WPF [调用线程无法访问此对象,因为另一个线程拥有该对象.] 解决方案以及如何实现字体颜色的渐变 先来看看C#中Timer的简单说明,你想必猜到实现需要用到Timer的相关知识了吧. C# ...
随机推荐
- 从GaussDB(DWS)的技术演进,看数据仓库的积淀与新生
摘要:随着云计算的兴起和渗透,云数仓成为了数仓技术演进的新阶段,并且逐渐成为了众多企业的共同选择. 本文分享自华为云社区<从GaussDB(DWS)的技术演进,看数据仓库的积淀与新生>,作 ...
- Unity的IPostGenerateGradleAndroidProject:深入解析与实用案例
Unity IPostGenerateGradleAndroidProject Unity是一款流行的跨平台游戏引擎,它支持多种平台,包括Android.在Unity中,我们可以使用IPostGene ...
- 获得 markdown 无序列表格式的文件目录树
tree 命令可以获得文件目录结构,但是放在文档中时,我想用 markdown 无序列表的形式,在编辑器内还可以折叠. 完整解决方案:在 ~/.oh-my-zsh/custom 下添加下述自定义函数( ...
- 【RabbitMQ】当队列中消息数量超过最大长度的淘汰策略
[RabbitMQ]当队列中消息数量超过最大长度的淘汰策略 说明 最近在研究RabbitMQ如何实现延时队列时发现消息进入死信队列的情况之一就是当消息数量超过队列设置的最大长度时会被丢入死信队列,看到 ...
- python 将中文数字转换成阿拉伯数字
日常遇到的中文数字主要有两种情况: 1."二零零一"这种类型,只包含[0-9]对应的十个中文字,需要转换成数字:2001.这种情况的转换十分简单. 2. "三百二十一&q ...
- 缕析条分Scroll属性
最近有项目需要使用js原生开发滑动组件,频繁要用到dom元素的各种属性,其中以各种类型的height和top属性居多,名字相近,含义也很容易搞混.因此特地总结归纳了一下常用的知识点,在文末我们来挑战实 ...
- 行行AI人才直播第11期:墨尔本大学数据科学高级讲师-宫明明《机器学习:从统计到因果,人工智能的发展之路》
行行AI人才是博客园和顺顺智慧共同运营的AI行业人才全生命周期服务平台. 马克斯·普朗克智能系统中心主任曾在国际数学家大会进行了题为 From Statistical to Causal Learni ...
- linux文本编辑YCM报错
linux文本编辑YCM报错 刚从github安装了vimplus,可是发现存在不少的问题.索性给直接记录一下. The ycmd server SHUT DOWN (restart with ':Y ...
- Git SSH 认证配置
[前言] 我们在开发过程中,经常会和github,gitlab或者gitee打交道,一般临时克隆(clone)其他人的项目学习参考时,我们大多采用 https 的方式进行 clone 但如果在参与多个 ...
- react中常见hook的使用方式与区别
1.什么是hook?react hook是react 16.8推出的方法,能够让函数式组件像类式组件一样拥有state.ref.生命周期等属性. 2.为什么要出现hook?函数式组件是全局当中一个普通 ...