如何在Python中从父进程传递文件描述符给子进程?

8

我正在使用multiprocessing模块,并使用池来启动多个工作进程。但是在工作进程中关闭了在父进程中打开的文件描述符。我希望它们保持打开状态..!是否有办法传递文件描述符以在父进程和子进程之间共享?


如前所述,您需要使用特定于操作系统的功能。您有兴趣支持哪些平台? - Rakis
我需要支持Windows和Linux,因此我不想使用任何特定于操作系统的功能。在Linux中,默认情况下共享文件句柄,而Windows也有一个选项可以在CreateProcess()期间共享文件句柄...我不知道为什么多进程模块没有额外的选项来共享文件句柄。 - kumar
1
由于Windows和Linux在传递文件句柄的语义上存在差异,您可能必须使用特定于操作系统的功能。不过这并不成问题,可以通过sys.platform来区分两者,并调用特定于操作系统的“让其针对该操作系统运行”的函数。建议阅读多进程模块的代码,以查看是否有简单的解决方法。 - Rakis
请问您能解释一下,您是如何判断它们已关闭的吗?根据我所了解的,在进程间传递文件描述符根本行不通(我没有找到更近一步的解释),那么您是如何知道该描述符已关闭而不是被传递或其他情况呢?谢谢。 - Wakan Tanka
4个回答

9

在Python 2和Python 3中,用于发送和接收文件描述符的函数存在于 multiprocessing.reduction 模块中。

示例代码(Python 2和Python 3):

import multiprocessing
import os

# Before fork
child_pipe, parent_pipe = multiprocessing.Pipe(duplex=True)

child_pid = os.fork()

if child_pid:
    # Inside parent process
    import multiprocessing.reduction
    import socket
    # has socket_to_pass socket object which want to pass to the child
    socket_to_pass = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
    socket_to_pass.connect("/dev/log")
    # child_pid argument to send_handle() can be arbitrary on Unix,
    # on Windows it has to be child PID
    multiprocessing.reduction.send_handle(parent_pipe, socket_to_pass.fileno(), child_pid)
    socket_to_pass.send("hello from the parent process\n".encode())
else:
    # Inside child process
    import multiprocessing.reduction
    import socket
    import os
    fd = multiprocessing.reduction.recv_handle(child_pipe)
    # rebuild the socket object from fd
    received_socket = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
    # socket.fromfd() duplicates fd, so we can close the received one
    os.close(fd)
    # and now you can communicate using the received socket
    received_socket.send("hello from the child process\n".encode())

1
send_handlerecv_handle在Python 2中也存在,正如我在这里中提到的那样,没有必要区分两者。此外,我不会使用发送套接字作为示例,因为multiprocessing使用其自定义Pickler注册了socket.socket - socket.socket对象可以直接通过Connection发送,而无需上面的任何代码,但如果保留原样,则可能会有人将其复制到自己的代码库中。 - Chris Hunt
好的,我看到它也被回溯到了Python 2。至于发送 socket.socket 而没有额外的代码,在任意管道/套接字上似乎不起作用,只适用于使用整个 multiprocessing.Manager 机制创建的那些。 - Piotr Jurkiewicz

3

还有一个名为multiprocessmultiprocessing分支,它使用dill替换了pickle。因为dill可以pickle文件描述符,所以multiprocess可以方便地在进程之间传递它们。

>>> f = open('test.txt', 'w')
>>> _ = map(f.write, 'hello world')
>>> f.close()
>>> import multiprocess as mp
>>> p = mp.Pool()
>>> f = open('test.txt', 'r')
>>> p.apply(lambda x:x, f)
'hello world'
>>> f.read()
'hello world'
>>> f.close()

@user765443:也许你的 multiprocess 安装有问题?你是在 Windows 上,没有 C 编译器吗? - Mike McKerns

2

multiprocessing本身有助手方法,用于在支持通过Unix域套接字发送文件描述符的Windows和Unix平台之间传输文件描述符:multiprocessing.reductionsend_handlerecv_handle。这些未记录在文档中,但在模块的__all__中,因此可以安全地假设它们是公共API的一部分。从源代码来看,自2.6+和3.3+以来就一直可用。

所有平台都具有相同的接口:

  • send_handle(conn, handle, destination_pid)
  • recv_handle(conn)

其中:

  • conn (multiprocessing.Connection):用于发送文件描述符的连接
  • handle (int):指向文件描述符/句柄的整数
  • destination_pid (int):正在接收文件描述符的进程的整数pid - 目前仅在Windows上使用

-1

据我所知,没有一种方法可以在进程之间共享文件描述符。如果存在一种方法,那么它很可能是特定于操作系统的。

我猜你需要在另一个层面上共享数据。


4
同意,但有针对特定操作系统的方法。 - unbeli
1
是的,我知道fork()会复制文件描述符,但在进程启动后有没有简单的方法来做到这一点? - Mattias Nilsson
1
是的:https://dev59.com/QnNA5IYBdhLWcg3wku3O可移植的方法在不同进程之间传递文件描述符 - Rakis
1
multiprocessing 本身具有处理特定于操作系统的细节的函数,详见此处 - Chris Hunt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接