【问题标题】:IPC with a Python subprocess带有 Python 子进程的 IPC
【发布时间】:2015-04-16 10:25:39
【问题描述】:

我正在尝试在 Python 中执行一些简单的 IPC,如下所示:一个 Python 进程使用subprocess 启动另一个进程。子进程将一些数据发送到管道中,父进程接收它。

这是我当前的实现:

# parent.py
import pickle
import os
import subprocess
import sys
read_fd, write_fd = os.pipe()
if hasattr(os, 'set_inheritable'):
    os.set_inheritable(write_fd, True)
child = subprocess.Popen((sys.executable, 'child.py', str(write_fd)), close_fds=False)
try:
    with os.fdopen(read_fd, 'rb') as reader:
        data = pickle.load(reader)
finally:
    child.wait()
assert data == 'This is the data.'
# child.py
import pickle
import os
import sys
with os.fdopen(int(sys.argv[1]), 'wb') as writer:
    pickle.dump('This is the data.', writer)

在 Unix 上这按预期工作,但如果我在 Windows 上运行此代码,我会收到以下错误,之后程序会挂起直到被中断:

Traceback (most recent call last):
  File "child.py", line 4, in <module>
    with os.fdopen(int(sys.argv[1]), 'wb') as writer:
  File "C:\Python34\lib\os.py", line 978, in fdopen
    return io.open(fd, *args, **kwargs)
OSError: [Errno 9] Bad file descriptor

我怀疑问题在于子进程没有继承write_fd 文件描述符。我该如何解决这个问题?

代码需要与 Python 2.7、3.2 以及所有后续版本兼容。这意味着解决方案不能依赖于PEP 446 中指定的文件描述符继承的更改是否存在。如上所述,它还需要在 Unix 和 Windows 上运行。

(回答几个明显的问题:我不使用multiprocessing 的原因是,在我的实际非简化代码中,这两个 Python 程序是具有不同设置模块的 Django 项目的一部分。这个意味着它们不能共享任何全局状态。此外,子进程的标准流正被用于其他目的,不能用于此目的。)

更新: 设置 close_fds 参数后,代码现在可以在 Unix 上的所有 Python 版本中运行。但是,它在 Windows 上仍然失败。

【问题讨论】:

  • 这并不能直接回答您的问题,但execnet 会这样做。
  • 我更喜欢一种不会引入额外依赖的解决方案,特别是因为我已经拥有了大部分。但如果我别无选择,我可能会选择这个。
  • 也许您可以深入研究他们的实现,看看他们是如何做到的。它适用于 Linux 和 Windows。

标签: python pipe subprocess ipc


【解决方案1】:

subprocess.PIPE 适用于所有平台。你为什么不直接用这个?

如果您想手动创建和使用os.pipe(),您需要注意Windows 不支持fork() 的事实。它宁可使用CreateProcess(),默认情况下不会让孩子继承打开的文件。但是有一种方法:每个文件描述符都可以显式继承。这需要调用 Win API。我已经在 gipc 中实现了这一点,请参阅 _pre/post_createprocess_windows() 方法 here

【讨论】:

    【解决方案2】:

    作为@Jan-Philip Gehrcke suggested,您可以使用subprocess.PIPE 代替os.pipe()

    #!/usr/bin/env python
    # parent.py
    import sys
    from subprocess import check_output
    
    data = check_output([sys.executable or 'python', 'child.py'])
    assert data.decode().strip() == 'This is the data.'
    

    check_output() 在内部使用stdout=subprocess.PIPE

    如果child.py 使用data = pickle.dumps(obj),您可以使用obj = pickle.loads(data)

    child.py 可以简化:

    #!/usr/bin/env python
    # child.py
    print('This is the data.')
    

    如果子进程是用 Python 编写的,那么为了获得更大的灵活性,您可以将子脚本作为模块导入并调用其函数而不是使用子进程。如果您需要在不同的进程中运行一些 Python 代码,您可以使用 multiprocessingconcurrent.futures 模块。

    如果您不能使用标准流,那么 您的 django 应用程序可以使用套接字相互通信

    我不使用多处理的原因是,在我的实际非简化代码中,这两个 Python 程序是具有不同设置模块的 Django 项目的一部分。这意味着它们不能共享任何全局状态。

    这似乎是假的。 multiprocessing 在后台也可以使用 subprocess 模块。如果您不想共享全局状态——不要共享它—​​—它是多个进程的默认设置。您可能应该针对您的特定案例提出更具体的问题,即如何组织项目各个部分之间的通信。

    【讨论】:

    • 我所指的全局状态是在任何直接或间接导入 Django 设置的导入模块中。我尝试创建一个 multiprocessing 进程来更改 DJANGO_SETTINGS_MODULE 环境变量的值,然后导入 Django,但这不起作用,因为在我自己的任何代码之前,Django 已经导入到子进程中跑的机会。
    • @Taymon:评论可能不适合回答它,这就是为什么我建议提出一个新问题。但是我看到了一个简单的解决方法:不要在主进程中过早导入,而是在子初始化程序中执行,设置 os.environ 并在那里导入任何必要的东西:唯一受影响的代码是产生多个进程的代码,其余的效果一样。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-25
    • 2020-05-12
    • 2017-07-27
    • 2015-01-03
    • 2019-12-01
    • 2012-11-22
    相关资源
    最近更新 更多