当产生一个新进程时,导入会发生啥?

Posted

技术标签:

【中文标题】当产生一个新进程时,导入会发生啥?【英文标题】:What happens to imports when a new process is spawned?当产生一个新进程时,导入会发生什么? 【发布时间】:2015-05-26 00:30:59 【问题描述】:

生成新进程时,导入的模块变量会发生什么情况?

IE

with concurrent.futures.ProcessPoolExecutor(max_workers=settings.MAX_PROCESSES) as executor:
    for stuff in executor.map(foo, paths):

地点:

  def foo(str):
  x = someOtherModule.fooBar()

foobar 正在访问在 someOtherModule 开头声明的内容:

someOtherModule.py:

 myHat='green'
 def fooBar():
   return myHat

具体来说,我有一个模块(称为 Y),它在顶部初始化了一个 py4j 网关,在任何函数之外。在模块 X 中,我一次加载多个文件,加载后对数据进行排序的函数使用 Y 中的函数,该函数又使用网关。

这个设计是pythonic吗? 我应该在每个新进程生成后导入我的 Y 模块吗?或者有更好的方法吗?

【问题讨论】:

Py4J 作者在这里。如果您从不同的进程访问同一个网关,请不要这样做,因为您将与多个进程共享一个套接字 Py4J 并非旨在处理这种情况(尽管它是线程安全的)!相反,为每个进程创建一个新的网关实例。 如果我想要我的 java 程序的多个实例怎么办?那能实现吗? (就像在新网关中为每个新的 multiprocessing.process 提供一个新的 java 实例一样) 可以,但是每个 Java 进程必须使用不同的端口(可以在创建 GatewayServer 时配置) 【参考方案1】:

在 Linux 上,fork 将用于生成子级,因此父级全局​​范围内的任何内容也将在子级中可用,具有写时复制语义。

在 Windows 上,您在父进程的 __main__ 模块中的模块级别 import 将在子进程中重新导入。

这意味着如果你有一个父模块(我们称之为someModule),如下所示:

import someOtherModule
import concurrent.futures

def foo(str):
    x = someOtherModule.fooBar()

if __name__ == "__main__":
    with concurrent.futures.ProcessPoolExecutor(max_workers=settings.MAX_PROCESSES) as executor:
        for stuff in executor.map(foo, paths):
            # stuff

someOtherModule 看起来像这样:

myHat='green'
def fooBar():
    return myHat

在本例中,someModule 是脚本的__main__ 模块。因此,在 Linux 上,您在子节点中获得的 myHat 实例将是 someModule 中实例的写时复制版本。在 Windows 上,每个子进程将在加载后立即重新导入 someModule,这将导致 someOtherModule 也被重新导入。

我对 py4j Gateway 对象知之甚少,无法确定您是否确定这是您想要的行为。如果Gateway 对象是可腌制的,您可以将其显式传递给每个孩子,但您必须使用multiprocessing.Pool 而不是concurrent.futures.ProcessPoolExecutor

import someOtherModule
import multiprocessing

def foo(str):
    x = someOtherModule.fooBar()

def init(hat):
    someOtherModule.myHat = hat

if __name__ == "__main__":
    hat = someOtherModule.myHat
    pool = multiprocessing.Pool(settings.MAX_PROCESSES,
                                initializer=init, initargs=(hat,))
    for stuff in pool.map(foo, paths):
            # stuff

不过,您似乎不需要为您的用例执行此操作。使用重新导入可能没问题。

【讨论】:

不知道 Windows 如何使用多处理模块。真的很有趣 我很久以前就知道这个发布日期,但我有一个关于 windows 的问题。当您说它“重新导入”模块时,您的意思是它重新导入为 sys.modules 或作为新的导入?它们会共享相同的变量还是 Windows 将其作为独立模块导入?谢谢! @Léo 这是一个全新的进口。 Windows 在子进程中重新执行__main__ 模块。【参考方案2】:

当您创建一个新进程时,会调用fork(),它会克隆整个进程和堆栈、内存空间等。这就是为什么认为多处理比多线程更昂贵,因为复制成本很高。

所以为了回答你的问题,所有“导入的模块变量”都被克隆了。您可以随意修改它们,但您的原始父进程不会看到此更改。

编辑: 这仅适用于基于 Unix 的系统。请参阅 Dano 对 Unix+Windows 的回答。

【讨论】:

我认为 windows 使用 spawn 是因为它没有 fork....对于 X 中的全局变量,我必须将它们传递给子进程,因为它们是空的。 @user2757902 没错。 Windows 上没有fork。在 Windows 上,子进程将重新导入该父进程的 __main__ 模块,以及需要导入的任何模块以取消您显式传递给子进程的任何参数,仅此而已。

以上是关于当产生一个新进程时,导入会发生啥?的主要内容,如果未能解决你的问题,请参考以下文章

当一个模块被导入两次时会发生啥?

当 .NET 线程抛出异常时会发生啥?

当 Promise 永远不会解决时会发生啥? [复制]

当 Impala 中的客户端触发查询时会发生啥?

iOS Safari Web 扩展 - 当我们添加新的所需权限时,当前用户会发生啥

当 Spark master 失败时会发生啥?