当产生一个新进程时,导入会发生啥?
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__
模块,以及需要导入的任何模块以取消您显式传递给子进程的任何参数,仅此而已。以上是关于当产生一个新进程时,导入会发生啥?的主要内容,如果未能解决你的问题,请参考以下文章