分离进程的Pythonic方式?

Pythonic way to detach a process?

我是 运行 一个 etcd 进程,在您终止它之前它一直处于活动状态。 (它不提供守护进程模式选项。)我想分离它以便我可以保留 运行 更多 python.

我会在 shell;

做什么
etcd & next_cmd

我正在使用 python 的 sh 库,受到整个互联网的热烈推荐。我不想深入研究 subprocessPopen,但我也没有找到使用它们的解决方案。

我想要的;

sh.etcd(detach=True)
sh.next_cmd()

sh.etcd("&")
sh.next_cmd()

不幸的是 detach 不是 kwarg 并且 sh"&" 视为 etcd 的标志。

我在这里遗漏了什么吗?这样做的好方法是什么?

请注意,在以下两个示例中调用了 time.sleep(...)etcd 时间在我们开始之前完成启动 向它发送一个请求。真正的解决方案可能涉及探测 API 端点以查看它是否可用,如果不可用则循环。

选项 1(滥用 multiprocessing 模块):

import sh
import requests
import time

from multiprocessing import Process

etcd = Process(target=sh.etcd)

try:
    # start etcd
    etcd.start()
    time.sleep(3)

    # do other stuff
    r = requests.get('http://localhost:4001/v2/keys/')
    print r.text
finally:
    etcd.terminate()

这使用 multiprocessing 模块来处理 生成后台任务。使用此模型,您将看不到 etcd.

的输出

选项 2(经过验证且正确):

import os
import signal
import time
import requests

pid = os.fork()
if pid == 0:
    # start etcd
    os.execvp('etcd', ['etcd'])

try:
    # do other stuff
    time.sleep(3)
    r = requests.get('http://localhost:4001/v2/keys/')
    print r.text
finally:
    os.kill(pid, signal.SIGTERM)

这使用传统的 forkexec 模型,其工作方式与 在 Python 中很好,就像在 C 中一样。在这个模型中,etcd 的输出 将显示在您的控制台上,这可能是您想要的,也可能不是您想要的。您可以通过在子进程中重定向 stdoutstderr 来控制它。

要实现 sh&,避免 cargo cult 编程并直接使用 subprocess 模块:

import subprocess

etcd = subprocess.Popen('etcd') # continue immediately
next_cmd_returncode = subprocess.call('next_cmd') # wait for it
# ... run more python here ...
etcd.terminate() 
etcd.wait()

这会忽略异常处理和您关于 "daemon mode" 的讨论(如果您想在 Python 中实现守护进程;请使用 python-daemon. To run a process as a system service, use whatever your OS provides or a supervisor program such as supervisord)。

这里是 sh 的作者。我相信你想使用 _bg 特殊关键字参数 http://amoffat.github.io/sh/#background-processes

这将立即分叉您的命令和 return。即使在您的脚本退出后,该过程仍将继续 运行。

如果下次我 google 同样的问题时发现它,没有其他原因就发布这个:

 if os.fork() == 0:
    os.close(0)
    os.close(1)
    os.close(2)
    subprocess.Popen(('etcd'),close_fds=True)
    sys.exit(0)

Popen close_fds 关闭除 0,1,2 以外的文件描述符,因此代码明确关闭它们。

子流程也很容易做到这一点:

这种方法有效 (python3)。关键是使用“start_new_session=True”

更新:尽管 Popen 文档说这是可行的,但实际上并没有。我发现通过分叉 child 然后执行 os.setsid() 它可以按我想要的方式工作

client.py:

#!/usr/bin/env python3
import time
import subprocess
subprocess.Popen("python3 child.py", shell=True, start_new_session=True)
i = 0
while True:
    i += 1
    print("demon: %d" % i)
    time.sleep(1)

child.py:

#!/usr/bin/env python3
import time
import subprocess
import os

pid = os.fork()
if (pid == 0):
    os.setsid()

    i = 0
    while True:
        i += 1
        print("child: %d" % i)
        time.sleep(1)
        if i == 10:
            print("child exiting")
            break

输出:

./client.py
demon: 1
child: 1
demon: 2
child: 2
^CTraceback (most recent call last):
  File "./client.py", line 9, in <module>
    time.sleep(1)
KeyboardInterrupt

$ child: 3
child: 4
child: 5
child: 6
child: 7
child: 8
child: 9
child: 10
child exiting