最近在搬一些把 Python 解释器嵌入到 C++中砖( Windows 下),虽然能搬,但并不是什么特别愉快的体验:要屏蔽应用对 Python 的显式依赖,所以通过 C 接口在 C++和 Python 之间传递数据和错误信息,C 接口下面又用 Cython 来进行数据转换和函数调用,基本上是这样的:
C++ ⇌ C ⇌ Cython ⇌ Python
还有个麻烦是这个过程中 C++,Cython ,Python 没法一起 debug ( C++用的 CLion 开发,Cython 和 Python 用的 PyCharm ),而且 Cython 的 debug 只能进入到生成的 cpp 中,不过还好,能定位问题在哪个 Cython 函数,很多时候只能在 Cython 中加 print 来看传递的参数 (如果有更好的办法请教教我 o(╥﹏╥)o)
总之,坑,特别多的坑,即使用了 Cython 不直接用 Python 的 C API 也要写不少代码,而且 Python 解释器在应用进程,如果没照顾好它在运行中的情绪,轻则自己崩掉,重则一起崩掉,不管哪种情况应用基本要重启了。
不清楚什么时候在哪儿看到过 RPC(Remote Procedure Call)的概念, 然后经过一番谷噜,看到了这篇博客 https://opensource.com/article/20/3/zeromq-c-python 作者开头提到的困境跟我现在经历的差不多(其实给 Python 写扩展还好啦,Python 生态就是这样来的,反过来把 Python 当成库用才更头疼)。作者提到 ZeroMQ 是这种问题的解决方案( ZeroMQ 我是在 V2 看到过相关讨论的,虽然当时可能并不清楚这东西多有用)。经过翻阅一些文档看了一些例子,我对以上背景问题也有了以下基于 ZeroMQ 的设想方案:
C++中启动一个 Python 脚本,该脚本运行在独立进程中作为一个服务等待请求; C++通过发送请求传递需要处理的参数给 Python ,Python 处理后把结果发送回去;要是传递的参数不合适导致服务崩了,没关系,重启该 Python 脚本继续服务,修改参数再传(如果是 Python 解释器嵌入 C++则只能启动一次关闭一次,无法重新启动);完事儿后 C++可以传递特殊参数优雅地停止该服务。而且开发时各个模块我都可以在各自的开发环境中 debug 了╰(°▽°)╯!
有了这种方式后的确打开了编写应用的新世界大门(即使仅仅是本地的桌面应用),用合适的语言和工具编写独立的功能模块,然后通过 ZeroMQ 将各个独立模块连接起来构成整个系统。
前景是美好的,但是实践起来肯定有坑,所以想请熟知 ZeroMQ 并用其在应用中摸爬滚打过的前辈们现身说法,谈谈自己对 ZeroMQ 看法,分享一些观点和经验,踩过的坑,缺点以及克服方法。
在我的观念里至少要了解到某个工具的一些基本的底层原理才能更好的使用该工具。ZeroMQ 是一个值得深入了解的工具。
明天调休继续上班,到时再来看各位的回复
1
roundgis 2023-01-14 23:32:56 +08:00 via Android 1
jetbrains rider 就是這樣做的
ui 用 java 啟動的時候會同時啟動一個.net backend 進程 兩者用 socket 通信 我之前有一個應用也用了類似的模式 ui c# backend golang |
2
ysc3839 2023-01-15 01:05:51 +08:00 via Android
关于原始问题,你是想用 Python 给 C++应用写插件吗?一般的做法是原应用暴露出一个 C 的插件接口,然后用 C/C++写个 DLL 对接 Python ,图省事可以用 pybind11 ,不麻烦,用 Cython 感觉更复杂了。
不过上述方案确实有不便于调试的问题,RPC 也是另一种方案。不过我个人不推荐用 ZeroMQ 来实现 RPC ,它本身不是针对 RPC 设计的,还需要你额外补充一些东西才能成为完整的 RPC 框架。可以考虑一下 grpc 。 |
3
xsen 2023-01-15 07:24:31 +08:00 1
真心建议不要从无到由这样通过 zmq 造 rpc ( N 年前造过,要稳定可产品化——工作量还是不小的)
grpc 或 nats 成熟的方案很香,都是用 proto 定义 protocol 的 |
4
xgdgsc 2023-01-15 08:32:48 +08:00 via Android 1
初学还是用 grpc jsonrpc 把功能实现了吧,如果对延迟要求不高。另外考虑下是否内嵌是有必要的,能不能纯用 cpp 纯用 julia 纯用 rust 代替,学新语言可能都比折腾 py cpp 两个麻烦的玩意要省事
|
5
justou OP @ysc3839 是的, 就是一个 C ABI 的 DLL 将整个 python 虚拟机隐藏在下面, 这样上层应用不论是 C++ C#还是其它什么实现都能用了. Cython 主要在 cdef public 的函数中对 C++传过来的指针进行数据转换然后再直接调用 python 函数. 既然已经到 C ABI 层次了, 可能 pybind11 与 Cython 区别不大了, 主要是我对 Cython 熟悉一点, 其实开发中最麻烦的还是 debug 问题, 两边无法直接联动.
@xsen @xgdgsc 改用 zmq 替换嵌入 python 虚拟机具体一点的想法是这样的: 还是一个 C ABI 的 DLL, 假设有图像处理和视频(几秒的视频)处理功能用 python 来实现, start_image_service, start_video_service 分别启动对应的 python 脚本在后台等待请求, process_image(void* image, ...), process_video(void* video, ...)将数据发送过去, 等待处理结果; python 接收到对应的字节后对其解码成适合自己的数据结构, 处理后发送回去. 不过随着应用的通信变得复杂, 这种方式可能会变得不可控, 看来大家说的 gprc 才是正确选择; 对延迟要求还是挺高的, 所以进程间通信越快越好. 其实我主要是想了解下 zmq 底层实现的一些思想, 也就是上面关于 ZeroMQ 的 1,2,3 个问题. |
6
xgdgsc 2023-01-15 11:25:44 +08:00 1
如果视频和图片数据可以用 memory map 方式共享的话,python 和 cpp 进程共享 mmap 后,zeromq 只用 reqrep 传递控制处理这个共享内存的指令,也是一种比较简单的通信方案. 延迟有要求最好不要 rpc. https://stackoverflow.com/a/69806149/1136027
|
7
xsen 2023-01-15 12:28:22 +08:00 1
@justou #5 底层就是 tcp/udp 。有些是 cs ,有些是 cs/cs (就是互为 server ),这个是跟不同的模式有关
你之所以有疑惑,是没做过裸 socket 的编程,没做过 cs 架构的东西 其实架构都是差不多的,1:N 的话,都是有个类似的 broker 做分发 我们之前是 zmq + protobuf ,架构就是 cs 架构 |
8
xsen 2023-01-15 12:30:23 +08:00
若是通过算法做图片或视频流的处理,一般图片或视频流(因为就几秒,类似的小文件)都是通过 kafka 来做的
因为消息队列来说,kakfa 吞吐量都是最大的。之前接触过一些做视频分析的,就是用 kafka |
9
xsen 2023-01-15 12:32:47 +08:00
除非对性能或延迟要求极高,不然是不建议通过封装 c/c++库的方法与第三方语言集成的,不管是维护、扩展都不太灵活
通过 mq 解耦是通用的做法 |
10
oneisall8955 2023-01-15 12:51:22 +08:00 via Android
实习时候接触过,没整明白。印象只是个类库,按需组合成自己的消息中间件
|
11
justou OP |
12
lolizeppelin 2023-01-15 15:54:46 +08:00
python rpc 直接用 openstack 组件 oslo_message
|
13
pppoe 2023-01-16 00:44:52 +08:00
我就是基于 zmq, 重构了项目代码, 进程内和进程间(网络)通讯都是 zmq 的. zmq 是我项目架构的灵魂
|
14
BingoXuan 2023-01-16 14:02:33 +08:00
zmq 最重要是不用考虑连接问题。不管是 tcp 还是 udp 还是 unix socket ,zmq 底层都帮你做好。zmq 并不希望开发者花费大量工夫去了解底层。
如果架构只用了 python 和 c++,你可以试一下用 pybind11 。底层序列化用 json 或者 msgpack 或者 flatbuffer 都可以,c++写业务上 entity 的 encode 和 decode ,生成 c++和 python 的库。无论谁是 server 或者 client 或者 p2p 都不怕 |