You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用pybind11编写会因Pickle序列化问题导致Multiprocessing失败的C++ Python模块?

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 15:14:32