3

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

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()

foob​​ar 正在访问在 someOtherModule 开始时声明的内容:

someOtherModule.py:

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

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

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

4

2 回答 2

3

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

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

这意味着如果您有一个父模块(我们称之为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重新导入。

我对 py4jGateway对象知之甚少,无法确定您是否确定这是您想要的行为。如果Gateway对象是可腌制的,您可以将其显式传递给每个孩子,但您必须使用 amultiprocessing.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

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

于 2015-03-23T17:17:39.903 回答
2

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

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

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

于 2015-03-23T04:42:18.143 回答