multiprocessing.Value配合Pool.map使用报错的原因及两种Value的区别疑问
multiprocessing.Value配合Pool.map使用报错的原因及两种Value的区别疑问
嗨,我来帮你把这个问题掰扯清楚!你在学习多进程共享变量时,发现用官方文档里的Process.start()配合multiprocessing.Value没问题,但换成Pool.map就抛出了RuntimeError: Synchronized objects should only be shared between processes through inheritance的报错,对吧?咱们结合你的代码和报错一步步拆解:
先看你的测试代码:
from multiprocessing import Value, Pool, Manager def f(args): n, = args n.value = 1 if __name__ == '__main__': n = Value('d', 0.0) # n = Manager().Value('d', 0.0) # can workaround the error with Pool(1) as pool: pool.map(f, [(n,)]) print(n.value)
对应的报错栈:
Traceback (most recent call last): File "D:\0ly\ly\processvaldoc.py", line 13, in <module> pool.map(f, [(n,)]) File "C:\a3\envs\skl\Lib\multiprocessing\pool.py", line 367, in map return self._map_async(func, iterable, mapstar, chunksize).get() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\a3\envs\skl\Lib\multiprocessing\pool.py", line 774, in get raise self._value File "C:\a3\envs\skl\Lib\multiprocessing\pool.py", line 540, in _handle_tasks put(task) File "C:\a3\envs\skl\Lib\multiprocessing\connection.py", line 206, in send self._send_bytes(_ForkingPickler.dumps(obj)) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\a3\envs\skl\Lib\multiprocessing\reduction.py", line 51, in dumps cls(buf, protocol).dump(obj) File "C:\a3\envs\skl\Lib\multiprocessing\sharedctypes.py", line 199, in __reduce__ assert_spawning(self) File "C:\a3\envs\skl\Lib\multiprocessing\context.py", line 374, in assert_spawning raise RuntimeError( RuntimeError: Synchronized objects should only be shared between processes through inheritance
一、为什么multiprocessing.Value在Pool.map里会报错?
咱们先搞懂multiprocessing.Value的本质:它是基于共享内存实现的——直接在父进程的内存空间里开辟一块所有子进程都能访问的区域。这种共享方式有个严格要求:只能通过「进程继承」的方式传递给子进程。
- 当你用
Process.start()创建子进程时,子进程会直接继承父进程的地址空间,自然就能拿到这块共享内存的引用,所以官方文档里的例子能正常运行。 - 但
Pool.map的逻辑不一样:进程池的子进程是提前预创建好的,当你给map传参数时,它会用pickle序列化的方式把参数“打包”传给子进程。而multiprocessing.Value这种绑定了父进程内存空间的对象,根本没法被正常序列化——序列化出来的只是个无效的空壳,所以就会抛出那个报错,提示你“同步对象只能通过继承在进程间共享”。
二、Manager().Value的“魔法”在哪里?
Manager其实是一个独立的代理服务进程,通过它创建的Value根本不是直接的共享内存对象,而是一个代理句柄:
- 这个代理对象可以被正常
pickle序列化,因为它本质上只是和管理进程通信的“联系方式”。 - 不管你用
Process还是Pool.map,子进程拿到这个代理后,都是通过和管理进程的IPC(进程间通信)来操作实际的共享值,所以它能在所有多进程场景下工作。 - 你说没用到
with Manager() as也能运行,是因为Manager创建后会在后台默默运行,但还是建议用上下文管理器(with语句)来确保它能被正确关闭,避免长期运行时的资源泄漏哦。
三、为什么要设计两种Value?
说白了就是性能和通用性的权衡:
multiprocessing.Value:直接操作共享内存,速度非常快,但局限性大——只能通过进程继承的方式共享,适合对性能要求高、用Process直接创建子进程的场景。Manager().Value:通过代理进程通信,速度会慢一些,但胜在通用性极强,能支持进程池、远程进程等所有多进程场景,而且还能扩展出共享字典、列表等更多类型的对象。
举个简单的例子:如果你的程序是开几个固定的子进程做计算,用原生Value更高效;如果需要用进程池动态管理大量子进程,那Manager().Value就是更稳妥的选择。
备注:内容来源于stack exchange,提问作者Lei Yang
相关产品推荐
相关产品推荐

