|
java通过jep(JavaEmbeddedPython)实现高效重复调用python脚本书接上回,之前我们通过命令行的方式调用python脚本并实现了传递大量数据的方案.但是新的问题再次出现:在需要频繁重复调用python脚本的应用环境下,命令行调用python脚本的方式每次都要新建进程->启动python解释器->执行脚本->关闭python解释器->关闭进程.由此导致命令行调用的方式效率低下.文章目录**java通过jep(JavaEmbeddedPython)实现高效重复调用python脚本**1.JavaEmbeddedPython2.JEP简单使用2.1环境搭建2.2下载jep2.3java中简单使用SharedInterpretersSubInterpretersDemo3.复杂场景下的使用具体场景1.初始化相机的回调函数2.回调函数实现3.监听事件调用python4.jep具体调用5.关闭jep总结1.JavaEmbeddedPython基于需要频繁调用python脚本的情景,引出我们本文的主角jep(JavaEmbeddedPython).jep是利用了JNI去调用CPythonAPI从而实现了java和python之间的通信.优点:python的解释器是启动在java进程中的,而且只需首次使用时启动,调用完成后不会自动关闭,相比命令行的方式节约了进程启动和python解释器启动的时间Java和Python的调用就像是函数调用,使用方便2.JEP简单使用2.1环境搭建Python>=3.5Java>=1.8NumPy>=1.7(optional)2.2下载jep执行pipinstalljep下载最新的jep,注意此下载不仅下载了jep的python库,jep.jar和jep.dll也在下载的文件夹下.maven依赖:black.ninia4.1.1123452.3java中简单使用最新的jep有两种解释器SharedInterpreters和SubInterpretersSharedInterpretersSharedInterpreters是在Jep3.8中引入的。SharedInterpreter是一个嵌入式Python解释器,不使用CPython子解释器API。可以将它们视为Jep/Java中的单个Python解释器,但大多数事情仍然是孤立的。具体来说,每个SharedInterpreter实例都有自己的一组全局变量,因此在一个SharedInterpreter中运行Python代码不会影响在另一个SharedInterpreter中运行代码,因为它们不共享作用域。然而,它们共享的是sys.modules,因此也是所有导入的模块。注意,只能为所有SharedInterpreter使用一个JepConfig实例,而每个SubInterpreter都可以有自己的JepConfig。这是因为SharedInterpreters本质上是相同的Python解释器,只是不同线程上有不同的全局变量。SubInterpretersSubInterpreters是原始的JepPython解释器。子解释器使用CPython子解释器API为每个线程创建一个子解释器。子解释器大多是沙盒的并且彼此隔离。例如,如果CPython扩展在其C代码中具有不考虑子解释器的全局静态变量,则该变量在进程中以及所有子解释器中仅存在一次。因此,更改该变量将导致更改所有子解释器的设置。子解释器可能会遇到CPython扩展的问题。由于大多数CPython扩展都是在没有考虑子解释器的情况下编写的,因此它们可能会出现问题,例如第二次初始化它们时(在第二个子解释器中)或子解释器关闭并清理内存时。为此,Jep在子解释器中添加了共享模块的概念。这些是您专门指定的模块,因为它们应该在子解释器之间共享注意:无论使用哪种解释器,当你创建一个Interpreter实例时,将为该JavaInterpreter实例创建一个Python解释器,并保留在内存中时,直到用Interpreter.close()关闭该Interpreter实例。由于需要管理一致的Python线程状态,创建Interpreter实例的线程必须在对该Interpreter实例的所有方法调用中重复使用.即你对Interpreter的操作(创建,调用,关闭)必须要同一个线程内,不可更换线程.Demo对于初学者来说,作者推荐使用SharedInterpreter.以下是一个简单的Demopackagecom.jscoe.backend.core.commons;importcom.alibaba.fastjson.JSONObject;importjep.Interpreter;importjep.JepConfig;importjep.MainInterpreter;importjep.SharedInterpreter;importjava.util.Arrays;publicclassDemo{publicstaticvoidmain(String[]args){test();}publicstaticvoidtest(){//主解释器加载jep.dll(pip下载的文件夹下)MainInterpreter.setJepLibraryPath("D:\\python\\Lib\\site-packages\\jep\\jep.dll");//设置python的项目路径JepConfigconfig=newJepConfig();config.addIncludePaths("D:\\workspace\\CompanyProjects\\tobacco-impurity-detection\\script");//SharedInterpreter.setConfig(config);try(Interpreterinterp=newSharedInterpreter()){JSONObjectrequestJson=newJSONObject();requestJson.put("var1",1);requestJson.put("var2",3);requestJson.put("var3",Arrays.toString(newbyte[]{1,2,3}));Objectresult;try{//等同于python中的fromtobacEngineimport*interp.eval("fromtobacEngineimport*");//python脚本tobacEngine中的方法result=interp.invoke("Demo",requestJson.toJSONString());System.out.println(result);//也可以直接混编//interp.exec("fromjava.langimportSystem");//interp.exec("s='HelloWorld'");//interp.exec("System.out.println(s)");//interp.exec("print(s)");//interp.exec("print(s[1:-1])");}catch(Exceptione){e.printStackTrace();}}}}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748493.复杂场景下的使用以上简单应用方式,Interpreter在执行完后还是会主动关闭,如果我们要重复调用就不能让他关闭.具体场景硬件相机每隔固定时间会获取到图像,获取到图像后会调用项目中的回调函数,回调函数内获取到图像后调用python算法去处理.这种情况下,我们在使用Interpreter调用python后不希望Interpreter关闭,因为过段时间我们还会重复调用Interpreter.具体实现:1.初始化相机的回调函数*****上下文省略*****Stringname="t-cc-"+System.currentTimeMillis();ThreadGroupthreadGroup=newThreadGroup("CameraCallbackThreadGroup");CustomOnImageGrabbedCallbackcallback=newCustomOnImageGrabbedCallback();callback.setMonoCamera(monoCamera);callback.setMFrameBuffer(mFrameBuffer);callback.setEventPublisher(eventPublisher);callback.setJepAlgorithmCompute(jepAlgorithmCompute);CallbackThreadInitializercallbackThreadInitializer=newCallbackThreadInitializer(true,false,name,threadGroup);Native.setCallbackThreadInitializer(callback,callbackThreadInitializer);*****上下文省略*****123456789101112Native.*setCallbackThreadInitializer*(callback,callbackThreadInitializer);可以保证每次回调都是在同一线程内执行的,即必须满足jep(创建,调用,关闭)必须要同一个线程内,不可更换线程.2.回调函数实现@Data@EqualsAndHashCode(callSuper=true)publicstaticclassCustomOnImageGrabbedCallbackextendsMVSDK.CAMERA_SNAP_PROC{privatelongmFrameBuffer;privatebooleanmonoCamera;privateApplicationEventPublishereventPublisher;privateJepAlgorithmComputejepAlgorithmCompute;@OverridepublicvoidOnCaptured(inthCamera,longpRawBuffer,MVSDK.tSdkFrameHeadpFrameHead,longContext){*****上下文省略*****//发布事件ReceiveImageOriginalDataSourcedataSource=ReceiveImageOriginalDataSource.builder().bands(1).createTime(System.currentTimeMillis()).width(width).height(height).data(sourceData).deviceType(DeviceTypeEnum.RGB_CAMERA).build();eventPublisher.publishEvent(ReceiveImageOriginalDataEvent.build(dataSource));*****上下文省略*****}}123456789101112131415161718192021222324252627283.监听事件调用python@Order(1)@EventListener@SneakyThrowspublicvoidexecuteAlgorithm(ReceiveImageOriginalDataEventevent){*****上下文省略*****//组装算法需要的参数ComputeRequestcomputeRequest= mergeCamerasImageData(receiveImageOriginalDataSource);//调用算法ComputeResponsecomputeResponse=jepAlgorithmCompute.compute(computeRequest);*****上下文省略*****}12345678910114.jep具体调用packagecom.jscoe.backend.core.device.script;importcom.alibaba.fastjson.JSONObject;importcom.jscoe.backend.core.device.jna.MVCameraManager;importcom.jscoe.backend.core.device.jna.enums.JetInterpStatus;importjep.SharedInterpreter;importlombok.RequiredArgsConstructor;importlombok.extern.slf4j.Slf4j;importorg.springframework.stereotype.Component;importjava.util.Arrays;importjava.util.Objects;/***@authorhonor*/@Component@Slf4j@RequiredArgsConstructorpublicclassJepAlgorithmCompute{privateSharedInterpreterinterp;//privatestaticInterpreterinterp;publicComputeResponsecompute(ComputeRequestcomputeRequest){if(Objects.isNull(interp)){interp=newSharedInterpreter();MVCameraManager.jetInterpStatus=JetInterpStatus.RUNNING;}*****上下文省略*****Objectresult;try{//TODOtime3=System.currentTimeMillis();interp.eval("fromtobacEngineimport*");result=interp.invoke("doAnalysis",requestJson.toJSONString());*****上下文省略*****}catch(Exceptione){e.printStackTrace();}returnresponse;}publicvoidcloseInterp(){if(Objects.isNull(interp)){return;}interp.close();interp=null;}}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535.关闭jep具体场景为更新相机参数后需要关闭jep,重启相机,重新创建jep的python解释器.重点是关的时候要去当时创建SharedInterpreter的线程中去调用interp.close();publicvoidclose(Setting.RgbCameraSettingsetting){settingManager.updateById(Setting.builder().id(SETTING_ID).rgbCameraSetting(setting).build());//参数修改后设备需要重启MVCameraManager.jetInterpCloseFlag=true;while(MVCameraManager.jetInterpStatus==JetInterpStatus.RUNNING){log.warn("jep解释器还未关闭,等待中...");try{Thread.sleep(1000);}catch(InterruptedExceptione){thrownewRuntimeException(e);}}//jepAlgorithmCompute.closeInterp();log.info("jep解释器已经关闭");mvCameraManager.tryConnectAll();}12345678910111213141516总结实际使用中我们需要综合看需求以及各种技术方案的特点来决定要使用哪种方式。Java调用Python,技术方案有两大类:External(外部调用)和Embedded(内置依赖)。Ex方案特点是视Python脚本为独立运行的进程,通过进程间通信的方法来调用。Ex方案的共通优点是,Java和Python运行时是完全脱钩的,对运行时的要求少。而且安全性好,不会因为Python脚本出现如进程崩溃等问题影响Java进程的稳定性,尤其适合Python代码不稳定,Bug多的场景。共通缺点是性能差于Em方案,而且接口的灵活性也不如Em方案(预先定义好的接口是唯一的调用入口)。典型的就是Runtime.exec,直接启动进程的方法。这种方法是最简单的,但是性能和灵活性在所有方法里面是最差的。适合1.只是偶尔调用脚本,对性能要求不高2.因为只能通过输入输出等手段通信,适合没有复杂的和脚本交互的需求。另一个典型的Ex方案是把Python做成独立的,一直运行的服务进程,Java通过RPC(远程过程调用)或者HTTP请求来调用Python。优点是部署灵活,甚至可以突破单个机器限制做成分布式。由于RPC,接口的灵活性优于直接启动进程。缺点就是上面说的。Em方案Em方案的特点是在Java进程里面嵌入执行Python的模块,把Python代码也当做Java代码一样对待。这类方案的优点和缺点正好和Ex方案相反。Jython和JEP是两个典型的Em方案。Jython和Java结合紧密些。JEP则兼容性好些,甚至C写的编译好的Python模块都可以随便调用。
|
|