在 Python 中,当使用 multiprocessing 模块(如 Process、Pool)或 concurrent.futures.ProcessPoolExecutor 向子进程传递数据时,这些数据需要通过 pickle 序列化后传输。因此,只有 可序列化(pickleable) 的对象才能被安全地传递给子进程。
什么是可序列化(pickleable)?
可序列化对象 是指能被 Python 的 pickle 模块成功序列化(转换为字节流)和反序列化(从字节流恢复)的对象。
常见的可序列化类型
| 类型 |
示例 |
说明 |
| 基本数据类型 |
int, float, str, bool, None |
最基础的可序列化类型 |
| 容器类型 |
list, tuple, dict, set, frozenset |
只要其中元素可序列化即可 |
| 自定义类实例 |
MyClass() |
前提是:类在模块顶层定义,且没有不可序列化的属性 |
| 函数(仅限全局函数) |
def my_func(): ... |
必须是模块顶层定义的函数,不能是局部函数或 lambda |
| 内置函数/方法 |
len, print, math.sqrt |
部分内置函数可序列化(但不推荐依赖其可移植性) |
| 数值库对象 |
numpy.ndarray(小数组) |
NumPy 数组通常可序列化,但大数组效率低 |
pathlib.Path |
Path('/tmp/file.txt') |
Python 3.6+ 支持 |
datetime 对象 |
datetime.datetime.now() |
标准库中的时间对象可序列化 |
示例(可序列化)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import pickle
data = { 'name': 'Alice', 'age': 30, 'scores': [85, 92, 78], 'active': True, 'config': {'debug': False, 'timeout': 10} }
serialized = pickle.dumps(data) deserialized = pickle.loads(serialized) print(deserialized)
class Person: def __init__(self, name): self.name = name
p = Person("Bob") serialized = pickle.dumps(p)
def worker(x): return x * 2
from multiprocessing import Pool with Pool() as pool: result = pool.map(worker, [1, 2, 3])
|
什么是不可序列化(not pickleable)?
不可序列化对象 是那些 pickle 无法保存其状态或行为的对象,通常是包含运行时上下文、内存地址、系统资源、闭包等的对象。
常见的不可序列化类型
| 类型 |
示例 |
原因 |
lambda 函数 |
lambda x: x + 1 |
无名称,无法被 pickle 找到定义 |
| 嵌套函数 / 局部函数 |
在另一个函数内部定义的函数 |
无法被模块级导入 |
| 类方法(绑定方法) |
obj.method |
绑定到特定实例,pickle 无法重建绑定关系 |
| 生成器 |
(x for x in range(10)) |
状态依赖执行上下文,无法捕获完整状态 |
| 文件对象 |
open('file.txt') |
持有操作系统文件描述符,不可跨进程传递 |
| 线程/锁/事件等同步原语 |
threading.Lock(), threading.Event() |
与特定线程/进程绑定,跨进程无效 |
| 数据库连接 |
sqlite3.connect(...) |
持有 socket 或本地句柄 |
| 网络连接/套接字 |
socket.socket() |
无法在进程间共享底层资源 |
| 闭包中引用的非全局变量 |
def outer(): x=1; def inner(): return x; return inner |
闭包环境(cell 对象)无法被 pickle 持久化 |
| 某些第三方库对象 |
cv2.VideoCapture(...), pygame.Surface |
内部封装 C 资源,无 Python 级序列化支持 |
| 动态创建的类 |
type('DynamicClass', (), {})() |
不在模块命名空间中,pickle 无法定位反序列化路径 |
| 含有不可序列化属性的类实例 |
class A: def __init__(self): self.f = lambda x: x |
属性是 lambda → 整个对象不可序列化 |
示例(不可序列化)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| import pickle from multiprocessing import Process
f = lambda x: x + 1 try: pickle.dumps(f) except Exception as e: print("Lambda 不可序列化:", e)
def make_func(): def local_func(x): return x * 3 return local_func
g = make_func() try: pickle.dumps(g) except Exception as e: print("局部函数不可序列化:", e)
with open('test.txt', 'w') as f: try: pickle.dumps(f) except Exception as e: print("文件对象不可序列化:", e)
import threading lock = threading.Lock() try: pickle.dumps(lock) except Exception as e: print("Lock 不可序列化:", e)
class BadClass: def __init__(self): self.handler = lambda x: x + 10
obj = BadClass() try: pickle.dumps(obj) except Exception as e: print("含 lambda 的对象不可序列化:", e)
|
如何解决不可序列化问题?
方法一:改用全局函数
错误做法(使用 lambda):
1
| pool.map(lambda x: x**2, data)
|
正确做法(定义全局函数):
1 2 3 4
| def square(x): return x ** 2
pool.map(square, data)
|
方法二:避免在类中存储不可序列化对象
不推荐:
1 2 3
| class Worker: def __init__(self): self.lock = threading.Lock()
|
推荐做法:只传递必要数据,在子进程中初始化资源:
1 2 3
| def worker_task(data): lock = threading.Lock()
|
方法三:使用 dill 替代 pickle(高级场景)
dill 是 pickle 的增强版,支持更多对象(如 lambda、嵌套函数),但不保证跨平台/跨 Python 版本兼容性,生产环境慎用:
1 2 3 4
| import dill
f = lambda x: x + 1 serialized = dill.dumps(f)
|
注意:multiprocessing 默认使用 pickle,无法直接替换为 dill。若需使用,必须重写底层序列化机制,例如借助 multiprocess 库(第三方,非标准库)。
方法四:将数据提取出来,只传递“纯数据”
错误做法(传递整个对象):
正确做法(仅传递配置参数):
1 2
| worker(db_config={'host': 'localhost', 'port': 5432})
|
可序列化 vs 不可序列化速查表
| 类型 |
是否可序列化 |
说明 |
int, str, float, bool, None |
是 |
基础类型 |
list, dict, tuple, set(内含可序列化元素) |
是 |
容器递归检查 |
| 全局函数(模块顶层定义) |
是 |
def func(): ... |
| 自定义类(无不可序列化属性) |
是 |
类必须可通过 sys.modules 定位 |
numpy.array |
是 |
小数组无问题,大数组慢且易 OOM |
datetime 对象 |
是 |
标准库支持 |
lambda 表达式 |
否 |
无 __name__,无法反序列化定位 |
| 局部函数 / 嵌套函数 |
否 |
作用域不在模块顶层 |
threading.Lock, Event, Queue |
否 |
与进程绑定,跨进程无效 |
| 文件对象、套接字、数据库连接 |
否 |
持有 OS 资源句柄 |
| 生成器 |
否 |
状态不可保存 |
| 类方法(绑定方法) |
否 |
obj.method 是 method 对象,非函数 |
动态类(type(...) 创建) |
否 |
无模块路径,pickle 无法 find_class |
含 lambda 的类实例 |
否 |
即使类本身可序列化,属性不可则整体失败 |
最佳实践建议
- 优先传递纯数据:字典、列表、字符串、数字等,避免复杂对象。
- 避免跨进程共享状态:进程间是隔离的,应通过输入-输出通信,而非共享内存(除非明确使用
multiprocessing.shared_memory)。
- **全局函数优于局部函数或 **
lambda:确保函数在模块顶层定义且可导入。
- 在子进程中初始化资源:如数据库连接、文件句柄、锁等,不要从主进程传递。
- 调试技巧:使用
pickle.dumps(obj) 提前验证:
1 2 3 4 5 6 7
| import pickle
try: pickle.dumps(your_object) print("可序列化") except Exception as e: print("不可序列化:", e)
|
该检查可快速定位问题,避免在多进程环境中出现难以调试的 RuntimeError: Cannot pickle... 崩溃。