如果创建了multiprocessing.Pool,Python子进程wait()将失败

时间:2013-07-21 01:37:10

标签: python subprocess multiprocessing

在一个使用subprocess到gzip输出的简单脚本中(使用subprocess.PIPE到外部命令的stdin),如果在创建子进程和关闭进程的stdin之间创建了multiprocessing.Pool对象, subprocess.wait()将永远挂起。

import multiprocessing
import subprocess

proc = subprocess.Popen(["gzip", "-c", "-"], 
                        stdout=open('filename', 'w'), stdin=subprocess.PIPE)
multiprocessing.Pool()
proc.stdin.close()
proc.wait()

将multiprocessing.Pool调用一行或一行调用可以防止出现问题。

我在Python 2.7.3(Linux)和Python 2.7.1(OS X)上遇到过这种情况。

显然,这是一个微不足道的例子 - 真正的用法要复杂得多。我也已经知道GzipFile了 - 我宁愿不使用它;使用子进程可以通过将gzipping分成单独的线程来获得更多的CPU使用率。

我无法看到简单地实例化Pool应该如何产生这种影响。

1 个答案:

答案 0 :(得分:7)

当您调用multiprocessing.Pool时,multiprocessing模块会创建几个新进程(使用os.fork或类似代码)。

默认情况下,在fork期间,新进程会继承所有打开的文件描述符。

当您使用subprocess.Popen参数调用subprocess.PIPE时,subprocess模块会创建一些新的管道文件描述符,以便向/从新进程发送数据。在这种特殊情况下,管道用于将数据从父进程(python)发送到子进程(gzip),gzip将退出 - 从而使proc.wait()完成 - 当全部对管道的写访问权消失了。 (这就是在管道上生成“EOF”的原因:该管道不再存在可写的文件描述符。)

因此,在这种情况下,如果您(所有在“原始”python进程中)按此顺序执行此操作:

  1. 创建管道
  2. 创建一些multiprocessing.Pool进程
  3. 将数据发送到gzip
  4. 将管道关闭到gzip
  5. 然后,由于fork的行为,每个Pool进程都有一个os.dup的write-to-gzip管道,所以gzip继续等待更多的数据,这些Pool进程可以(但永远不要)发送。一旦Pool进程关闭其管道描述符,gzip进程就会退出。

    将其修复为真实(更复杂)的代码可能非常重要。理想情况下,您希望multiprocessing.Pool知道(奇怪地,不知何故)应保留哪些文件描述符,哪些不应该保留,但这并不像“只是在创建的子项中关闭一堆描述符”那么简单过程“:

    output = open('somefile', 'a')
    def somefunc(arg):
        ... do some computation, etc ...
        output.write(result)
    pool = multiprocessing.Pool()
    pool.map(somefunc, iterable)
    

    显然,output.fileno()必须由此处的工作进程共享。

    您可以尝试使用Pool的{​​{1}}来调用initializer(或fd列表中的proc.stdin.close),但是您需要安排保留跟踪描述符的跟踪。重构代码可能最简单,以避免“在错误的时间”创建池。