【问题标题】:Spawn a process in Python without forking在 Python 中生成一个进程而无需分叉
【发布时间】:2017-07-19 18:37:04
【问题描述】:

我正在使用 Python (2.7) 和 pymongo (3.3),我需要生成一个子进程来异步运行作业。不幸的是,pymongo 不是here 所述的 fork-safe(我需要在生成子进程之前与 db 交互)。

我使用subprocess.Popen(将shell 设置为True,然后设置为False)和multiprocessing.Process 运行了一个实验。据我所知,他们都 fork 父进程来创建子进程,但只有 multiprocessing.Process 导致 pymongo 打印它检测到分叉进程的警告。

我想知道这样做的 pythonic 方式是什么。似乎os.system 可能会为我做这件事,但subprocess 被描述为os.system 的预期替代品,所以我想知道我是否遗漏了什么。

【问题讨论】:

    标签: python subprocess pymongo python-2.x python-multiprocessing


    【解决方案1】:

    如果您能够迁移到 Python 3.4 或更高版本,则可以在使用 pymongo 之前,将您的 multiprocessing start method 设置为 'forkserver'。这会立即派生一个 fork 服务器进程,并且所有将来使用 multiprocessing 都会派生该 fork 服务器,而不是您的主进程。所以一旦设置了fork server,你的主进程就可以使用pymongo,fork server 不会使用它,所以refork不会有问题。

    遗憾的是,启动方法仅在 3.4 中添加,因此它不是 2.7 的选项,但如果其他人遇到此问题,它可能对他们有用。

    【讨论】:

    • 是的,这正是我所希望的。我现在看到 pymongo 将在 fork/exec 之后工作,但是分叉​​一些新的不相关进程对我来说是一种更干净的方式来生成子进程(也许我的 windows 根正在显示)。不幸的是,python 2->3 感觉我们的代码库还需要几年的时间:(
    【解决方案2】:

    不安全的 fork 并不意味着你不能调用 fork...它只是意味着子进程不应该使用任何继承的 PyMongo 实例。当您使用subprocess.Popen 时,新分叉的孩子几乎立即调用exec 以替换为shell 实例(shell = True)或所需的可执行文件(shell = False)。所以从 PyMongo 的角度来看是安全的。

    相反,当您调用 multiprocessing.Process 时,子进程确实是父进程的副本,并且确实保留了其 PyMongo 实例。因此在这种情况下使用 PyMongo 是不安全的,并且警告消息已正确发出

    【讨论】:

      【解决方案3】:

      我想你误会了;由于 PyMongo 的文档警告您单个 MongoClient 不是 fork 安全的,因此您将其解释为 PyMongo 禁止您的整个程序创建子进程。

      任何单个 MongoClient 都不是 fork-safe,这意味着您不能在 fork 之前创建它并在 fork 之后使用相同的 MongoClient 对象。在整个程序中使用 PyMongo,或者在 fork 之前使用一个 MongoClient 并在之后使用另一个,都是安全的。

      这就是为什么subprocess.Popen 没问题:你 fork,然后 exec(在子进程中用不同的程序替换你的程序),因此你以后不可能在子进程中使用相同的 MongoClient。

      引用PyMongo FAQ:

      在 Unix 系统上,多处理模块使用 fork() 生成进程。使用带有 fork() 的 MongoClient 实例时必须小心。具体来说,MongoClient 的实例不能从父进程复制到子进程。相反,父进程和每个子进程必须创建自己的 MongoClient 实例。例如:

      # Each process creates its own instance of MongoClient.
      def func():
          db = pymongo.MongoClient().mydb
          # Do something with db.
      
      proc = multiprocessing.Process(target=func)
      proc.start()
      

      永远不要这样做:

      client = pymongo.MongoClient()
      
      # Each child process attempts to copy a global MongoClient
      # created in the parent process. Never do this.
      def func():
        db = client.mydb
        # Do something with db.
      
      proc = multiprocessing.Process(target=func)
      proc.start()
      

      由于fork()、线程和锁之间固有的不兼容,从父进程复制的MongoClient实例在子进程中很可能发生死锁。如果有可能发生这种死锁,PyMongo 将尝试发出警告。

      【讨论】:

      • 啊哈是有道理的。我也很好奇在子进程中继承文件描述符(特别是套接字句柄)的任何潜在副作用,但我猜 Popen 的 close_fds 参数也解决了这个问题
      • PyMongo 使用 FD_CLOEXEC 创建其套接字,因此无论您是否通过 close_fds,这些描述符都会关闭。
      猜你喜欢
      • 2012-02-27
      • 1970-01-01
      • 2017-06-04
      • 1970-01-01
      • 1970-01-01
      • 2021-05-31
      • 2022-01-08
      • 2012-04-19
      • 2019-08-06
      相关资源
      最近更新 更多