You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Python多进程spawn模式下共享os.open创建的文件描述符的问题

Python多进程spawn模式下共享os.open创建的文件描述符的问题

嗨,我来帮你搞定这个困扰!你遇到的问题完全是spawn模式和fork模式的核心差异导致的,咱们一步步拆解原因和解决方案:

首先,为什么POSIX系统(默认fork模式)没问题,Windows(默认spawn模式)就不行?

  • fork模式下,子进程会直接复制父进程的整个地址空间和文件描述符表,所以父进程的fd在子进程里直接能用;
  • 但spawn模式不一样,它会重新启动一个全新的Python解释器,导入你的代码后再执行目标函数,父进程的文件描述符不会自动带过去——哪怕你设了os.set_inheritable也没用,因为multiprocessing默认不会主动传递这些“非必要”的资源(文档里说的“必要资源”指的是它内部通信用的管道之类的,不是咱们自己的文件描述符)。

那该怎么解决呢?咱们需要显式地把文件描述符传递给子进程,这里可以用multiprocessing.reduction模块里的工具来实现,它专门用来处理这种跨进程的资源传递。

给你改好的代码,直接就能在Windows的spawn模式下运行:

import multiprocessing
import os
from multiprocessing.reduction import send_handle, recv_handle
import sys

def read_file(conn):
    # 从管道接收父进程传来的文件描述符
    fd = recv_handle(conn)
    try:
        print(os.read(fd, 1024))
    finally:
        os.close(fd)
        conn.close()

if __name__ == '__main__':
    # 先准备测试文件
    with open('test.txt', 'wb') as fp:
        fp.write(b'hello world')
        
    fd = os.open('test.txt', os.O_RDONLY)
    try:
        # 标记fd为可继承,这一步不能少
        os.set_inheritable(fd, True)
        # 创建管道用来传递fd
        parent_conn, child_conn = multiprocessing.Pipe()
        p = multiprocessing.Process(target=read_file, args=(child_conn,))
        p.start()
        # 把fd通过管道传给子进程
        send_handle(parent_conn, fd, sys.stdout.fileno())
        p.join()
    finally:
        os.close(fd)

运行这个代码,你就能看到输出b'hello world'啦!

另外再补充几个关键点:

  • 一定要调用os.set_inheritable(fd, True),否则send_handle也没法传递这个fd;
  • 传递完成后记得在父子进程里都关闭fd,避免资源泄漏;
  • 如果你用的是Python的文件对象(不是os.open返回的fd),其实也可以用类似的方式传递,不过直接用文件对象的话,在spawn模式下可能会有pickle的问题,所以用fd+reduction的方式更可靠。

备注:内容来源于stack exchange,提问作者Alex

火山引擎 最新活动