首页 > 解决方案 > 确定在 Python 多处理期间腌制的确切内容

问题描述

正如线程中所解释的,当我调用 multiprocessing.Process 时被腌制的是什么?在某些情况下,多处理几乎不需要通过酸洗传输数据。例如,在 Unix 系统上,解释器用于fork()创建进程,并且在多处理启动时已经存在的对象可以被每个进程访问而无需腌制。

但是,我正在尝试考虑“这就是它应该如何工作”之外的场景。例如,代码可能存在错误,并且本应为只读的对象被无意修改,导致其酸洗转移到其他进程。

有什么方法可以确定在多处理过程中腌制了什么,或者至少腌制了多少?这些信息不一定必须是实时的,但如果有一种方法可以获得一些统计数据(例如,腌制的对象的数量),这可能会暗示为什么某些事情需要更长的时间来运行,这将是有帮助的由于意外的酸洗开销而超出预期。

我正在寻找 Python 环境内部的解决方案。进程跟踪(例如 Linux strace)、网络侦听、通用 IPC 统计信息以及可用于计算进程之间移动字节数的类似解决方案,都不足以具体识别对象酸洗与其他类型的通信。


更新:令人失望的是,除了破解模块和/或解释器源之外,似乎没有办法收集酸洗统计信息。但是,@aaron 确实解释了这一点并澄清了一些小问题,所以我接受了答案。

标签: pythonmultiprocessingpickle

解决方案


Multiprocessing 并不完全是一个简单的库,但是一旦您熟悉了它的工作原理,就很容易四处寻找并弄清楚。

您通常希望从context.py开始。这是所有有用的类根据操作系统绑定的地方,并且......嗯......你活跃的“上下文”。有 4 个基本上下文:Fork、ForkServer 和 Spawn for posix;和一个单独的 Windows Spawn。这些又各自有自己的“Popen”(称为 at start())来启动一个新进程来处理单独的实现。

popen_fork.py

创建一个进程从字面上调用os.fork(),然后在子进程中组织运行BaseProcess._bootstrap(),其中设置了一些清理内容,然后调用self.run()以执行您给它的代码。以这种方式启动进程不会发生酸洗,因为整个内存空间都被复制了(有一些例外。参见: fork(2))。

popen_spawn_xxxxx.py

我最熟悉 Windows,但我假设 win32 和 posix 版本都以非常相似的方式运行。一个新的 python 进程是用一个简单的命令行字符串创建的,包括一对管道句柄来读/写。新进程将导入 __main__ 模块(通常等于sys.argv[0]),以便访问所有需要的引用。然后它将执行一个简单的引导函数(来自命令字符串),该函数尝试从创建对象的管道中读取和取消腌制Process对象。一旦它有了Process实例(一个新对象,它是一个副本;不仅仅是对原始对象的引用),它将再次安排调用_bootstrap().

popen_forkserver.py

第一次使用“forkserver”上下文创建新进程时,将“生成”一个新进程,运行一个处理新进程请求的简单服务器(在管道上侦听)。随后的流程请求都转到同一台服务器(基于导入机制和服务器实例的模块级全局)。然后从该服务器“分叉”新进程,以节省启动新 python 实例的时间。然而,这些新进程不能拥有任何相同的(如在同一个对象中而不是副本中)Process对象,因为它们派生自的 python 进程本身就是“生成”的。因此Process实例被腌制并发送很像“spawn”. 这种方法的好处包括: 进行分叉的进程是单线程的,以避免死锁。启动一个新的 python 解释器的成本只支付一次。由于“fork”通常使用写时复制内存页,解释器的内存消耗以及 __main__ 导入的任何模块都可以在很大程度上共享。


在所有情况下,一旦发生拆分,您应该考虑将内存空间完全分开,并且它们之间的唯一通信是通过管道或共享内存。锁和信号量由扩展库(用 c 编写)处理,但基本上是由操作系统管理的命名信号量。Queue's, Pipe's 和multiprocessing.Manager's 使用酸洗来同步对它们返回的代理对象的更改。new-ishmultiprocessing.shared_memory使用内存映射文件或缓冲区来共享数据(由操作系统管理,如信号量)。

为了解决您的问题:

代码可能存在错误,并且本应为只读的对象被无意修改,导致其酸洗转移到其他进程。

这仅适用于multiprocessing.Manager代理对象。由于其他所有事情都要求您非常有意识地发送接收数据,或者使用除了酸洗之外的其他传输机制。


推荐阅读