利用信号量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# ...
随机推荐
- php屏蔽非正常访问和检测用户登录检测
<?phpnamespace Manage\Controller;use Common\Controller\DefaultController;class BaseController ext ...
- PostgreSQL 12 文档: 部分 IV. 客户端接口
部分 IV. 客户端接口 这一部分描述和PostgreSQL一起发布的客户端编程接口.这些章中的每一个都能被独立阅读.注意,还有很多用于客户端程序的其他编程接口是被独立发布的并且包含它们自己的文档(附 ...
- Educational Codeforces Round 151 (Rated for Div. 2) A-D
A 代码 #include <bits/stdc++.h> using namespace std; using ll = long long; bool solve() { int n, ...
- P3047 [USACO12FEB]Nearby Cows G 题解
P3047 [USACO12FEB]Nearby Cows G 题目描述 思路 使用换根DP, 设 \(dp[i][j]\) 表示以 \(i\) 为根节点的子树中深度小于等于 \(j\) 的点的权值之 ...
- 【Redis】模糊查询
Redis模糊查询 1.支持的通配符*.?.[] 2.通配符* a.单个 * 模式 # 查询所有的key keys * b.双 * 模式,匹配任意多个字符 # key中含有rich的key keys ...
- 如何有效检测、识别和管理 Terraform 配置漂移?
作者|Krishnadutt Panchagnula 翻译|Seal软件 链接|https://betterprogramming.pub/detecting-identifying-and-mana ...
- 五分钟教你使用GitHub寻找优质项目
前言 经常会有同学会问如何使用GitHub找到自己想要的项目,今天咱们就出一期快速入门教程五分钟教你使用GitHub寻找优质项目.GitHub作为世界上最大的项目开源平台之一,上面有着无数优质的开源项 ...
- disk test use sysbench and fio
sysbench 进入到测试目录 prepare.sh sysbench --test=fileio --file-test-mode=$1 --file-num=100 --file-total-s ...
- XCTF-favorite_number
题目 (xctf.org.cn) 判断,既要数组强等于,又要首元素不等:php有下标越界溢出漏洞,因此0可以通过整型溢出得到 绕过if判断 playload:stuff[4294967296]=adm ...
- VueX报错:Cannot read property 'commit' of undefined
原因 main.js文件中没有引入store 解决方案 添加如下代码即可 import store from "./store"; new Vue({ el: '#app', ro ...