如何用pybind11编写会因Pickle序列化问题导致Multiprocessing失败的C++ Python模块?
嘿,我完全懂你的困惑——你本来以为用pybind11写的C++模块在多进程里会因为pickle问题翻车,但实际却正常跑起来了,对吧?这其实和Linux下multiprocessing的默认启动方式以及你传递的任务类型有关。让我一步步给你解释,然后教你怎么构造出会触发pickle错误的场景。
为什么你的当前代码没失败?
你的测试代码能正常运行,主要有两个原因:
- Linux默认用fork模式启动子进程:fork会完整复制父进程的地址空间,包括已经加载的
my_module模块。子进程不需要重新导入模块,也不需要pickle模块本身,直接就能用已加载的C++函数。 - 你传递的是Python wrapper函数:
parallel_add是纯Python函数,pickle它的时候只需要保存函数名称和所在模块,子进程导入你的脚本后就能找到这个函数,进而调用已经加载的my_module.add——整个过程根本不需要pickle C++函数或者模块对象。
怎么构造出pickle失败的场景?
我们需要让multiprocessing不得不去序列化那些pybind11创建的、默认不支持pickle的对象(比如C函数本身、自定义C类的实例),或者切换到spawn模式(这种模式下必须序列化所有任务数据)。
方法一:直接传递C++函数给multiprocessing
把你的测试代码改成直接传递my_module.add给pool.map,去掉Python wrapper:
import my_module from multiprocessing import Pool if __name__ == "__main__": numbers = [1, 2, 3, 4, 5] try: with Pool(processes=2) as pool: # 直接传递C++实现的add函数 results = pool.map(my_module.add, numbers) print(results) except Exception as e: print(f"Multiprocessing error: {e}")
运行后你会看到类似这样的错误:
Multiprocessing error: cannot pickle 'builtin_function_or_method' object
原因是my_module.add是pybind11创建的扩展类型函数对象,默认不支持pickle,当multiprocessing尝试序列化它时就会触发失败。
方法二:使用spawn模式+传递自定义C++类实例
先修改你的C++模块,添加一个简单的自定义类:
#include <pybind11/pybind11.h> int add(int input_number) { return input_number + 10; } // 添加一个自定义C++类,用来测试pickle class Counter { private: int count = 0; public: void increment() { count++; } int get_count() { return count; } }; PYBIND11_MODULE(my_module, m) { m.doc() = "A module to test multiprocessing pickling issues."; m.def("add", &add, "Add 10 to a number"); // 绑定自定义类 pybind11::class_<Counter>(m, "Counter") .def(pybind11::init<>()) .def("increment", &Counter::increment) .def("get_count", &Counter::get_count); }
重新编译模块后,用spawn模式运行以下测试代码:
import my_module from multiprocessing import Pool, get_context def update_counter(counter): counter.increment() return counter.get_count() if __name__ == "__main__": # 切换到spawn启动模式(Windows默认用这个,Linux需要显式指定) with Pool(processes=2, context=get_context("spawn")) as pool: counter = my_module.Counter() # 尝试传递Counter实例给子进程,需要pickle序列化 results = pool.map(update_counter, [counter]*5) print(results)
这时候你会得到pickle错误:
Multiprocessing error: cannot pickle 'my_module.Counter' object
原因是spawn模式下,子进程会从头启动Python解释器,重新导入模块,并且所有传递给子进程的任务数据都必须被pickle序列化。而pybind11默认不给自定义C++类添加pickle支持,所以Counter实例无法被序列化。
额外小提示
如果想让自定义C++类支持pickle,你可以在pybind11的绑定代码中手动添加序列化逻辑,但这就偏离了你想要构造失败场景的初衷啦——默认不添加的话,就会触发你预期的pickle错误。
备注:内容来源于stack exchange,提问作者Simd

