如何pickle通过pybind11封装的C++对象以支持Python多进程数据共享?
pybind11封装C++对象的pickle序列化解决方案
问题根因
pybind11默认不会为绑定的C类自动生成pickle序列化逻辑,Python多进程的跨进程数据传输依赖pickle序列化,因此直接传递未注册序列化逻辑的C对象会抛出TypeError: can't pickle xxx objects的异常,不管是用全局变量传参还是multiprocessing.Manager.Queue存储都绕不开这个限制。
方案1:可修改pybind11绑定源码(最优解)
直接在类绑定时注册pickle序列化规则,示例代码如下:
#include <pybind11/pybind11.h> #include <pybind11/stl.h> namespace py = pybind11; PYBIND11_MODULE(cityflow, m) { py::class_<Archive>(m, "Archive") // 原有绑定逻辑,比如构造函数、方法绑定 .def(py::init<>()) .def("some_method", &Archive::some_method) // 新增pickle绑定 .def(py::pickle( // __getstate__:把C++对象状态转为可序列化的Python对象 [](const Archive& obj) { // 这里根据Archive的实现,把核心状态导出为字节、元组等可序列化结构 std::string state = obj.dump_serialized_state(); return py::bytes(state); }, // __setstate__:用序列化的状态重建C++对象 [](py::bytes state) { std::string state_str = state; Archive obj; obj.load_serialized_state(state_str); return obj; } )); }
完成绑定重新编译包后,cityflow.Archive对象就可以直接被pickle序列化,正常存入Queue或者跨进程传递。
方案2:无法修改pybind11绑定源码(第三方包场景)
如果用的是预编译好的第三方包,没有权限修改绑定代码,就手动在Python层做序列化包装:
- 先基于C++对象暴露的公开接口,实现两个辅助函数:
serialize_archive(arc: cityflow.Archive) -> bytes:调用Archive对象的导出接口,把状态转成二进制串、配置字符串等可序列化内容deserialize_archive(state: bytes) -> cityflow.Archive:用序列化的状态调用构造接口,重建Archive对象
- 对你的现有逻辑做最小改造:
- 往Queue存数据时,先序列化C++对象再存入:
tmp_arc = Node(serialize_archive(tmp), num.value, tmp_flow) node_queue.put(tmp_arc)
- 从Queue取数据时,先反序列化重建对象再使用:
current_archive = node_queue.get() real_archive = deserialize_archive(current_archive.archive) eng.load(real_archive)
如果C对象没有暴露任何导出/导入状态的接口,无法手动做序列化,就不要跨进程传递对象实例,改为每个子进程独立初始化所需的C对象,进程间只传递构造对象需要的参数即可。
内容的提问来源于stack exchange,提问作者SN.Tao
相关产品推荐
相关产品推荐

