利用信号量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# ...
随机推荐
- IOS开发-UIImageView基本用法
UIImageView是iOS中用于显示图像(图片.gif.svg等)的视图. 它的主要功能有: 1. 显示图片UIImageView可以通过image属性显示一张UIImage类型的图片.可以是本地 ...
- java BigDecimal解决浮点数的精度丢失和大数计算问题
java BigDecimal解决浮点数的精度丢失和大数计算问题 抛出浮点数问题: 先考个题,输入什么? System.out.println(0.1 + 0.2); 答案:0.30000000000 ...
- 图像处理_Retinex图像增强
单尺度SSR (Single Scale Retinex) 图像 S ( x , y ) S(x,y) S(x,y)分解为两个不同的图像:反射图像 R ( x , y ) R(x,y) R(x,y), ...
- Microsoft edge锁定在任务栏上,被修改主页360的解决方法
今天从桌面下边的任务栏打开Microsoft edge浏览器,突然发现主页被篡改为360导航了(生气!恶龙咆哮ooo 在桌面上是Microsoft edge,固定到任务栏就成为Microsoft ed ...
- 进程相关API
ID与句柄 如果我们成功创建一个进程,CreateProcess函数会给我们返回一个结构体,包括四个数 据:进程编号(ID).进程句柄.线程编号(ID).线程句柄. 进程ID其实我们早见过了,通常我们 ...
- quarkus依赖注入之五:拦截器(Interceptor)
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<quarkus依赖注入> ...
- 【日常踩坑】修复 chrome 打不开微信或者部分第三方应用内链接
目录 默认浏览器为 chrome 时,打不开微信或者部分第三方应用内链接(或者没有反应) 修复问题:卸载 KGChromePlugin 参考资料 默认浏览器为 chrome 时,打不开微信或者部分第三 ...
- Java爬虫实战系列——常用的Java网络爬虫库
常用的Java网络爬虫库 Java 开发语言是业界使用最广泛的开发语言之一,在互联网从业者中具有广泛的使用者,Java 网络爬虫可以帮助 Java 开发人员以快速.简单但广泛的方式为各种目的抓取数据. ...
- 从零开发Java入门项目--十天掌握
原文网址:从零开发Java入门项目--十天掌握_IT利刃出鞘的博客-CSDN博客 简介 这是一个靠谱的Java入门项目实战,名字叫蚂蚁爱购.从零开发项目,视频加文档,十天就能学会开发Java项目, ...
- ViTPose+:迈向通用身体姿态估计的视觉Transformer基础模型
身体姿态估计旨在识别出给定图像中人或者动物实例身体的关键点,除了典型的身体骨骼关键点,还可以包括手.脚.脸部等关键点,是计算机视觉领域的基本任务之一.目前,视觉transformer已经在识别.检测. ...