让 subprocess.Popen 只等待它的子进程到 return,而不是任何孙子进程
Have subprocess.Popen only wait on its child process to return, but not any grandchildren
我有一个 python 脚本可以执行此操作:
p = subprocess.Popen(pythonscript.py, stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=False)
theStdin=request.input.encode('utf-8')
(outputhere,errorshere) = p.communicate(input=theStdin)
它按预期工作,它等待子进程通过 p.communicate() 完成。但是在 pythonscript.py 中,我想“触发并忘记”一个“孙子”进程。我目前正在通过覆盖连接函数来做到这一点:
class EverLastingProcess(Process):
def join(self, *args, **kwargs):
pass # Overwrites join so that it doesn't block. Otherwise parent waits.
def __del__(self):
pass
然后这样开始:
p = EverLastingProcess(target=nameOfMyFunction, args=(arg1, etc,), daemon=False)
p.start()
这也很好,我只是在 bash 终端或 bash 脚本中 运行 pythonscript.py。控制和响应 returns,而 EverLastingProcess 启动的子进程继续运行。但是,当我 运行 pythonscript.py 和 Popen 运行 进行如上所示的过程时,从时间上看,Popen 正在等待孙子完成。
我怎样才能让 Popen 只等待子进程,而不是任何孙进程?
编辑: 以下在 Python 升级后停止工作,请参阅 Lachele 接受的答案。
同事的工作答案,改为shell=True,像这样:
p = subprocess.Popen(pythonscript.py, stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=True)
我已经测试过,孙子进程在子进程 returns 之后保持活动状态,而无需等待它们完成。
当我们最近升级 Python 时,上面的解决方案(使用 join
方法和 shell=True
添加)停止工作。
互联网上有很多关于这部分内容的参考资料,但我花了一些时间才想出一个对整个问题有用的解决方案。
以下解决方案已在 Python 3.9.5 和 3.9.7 中测试。
问题概要
脚本的名称与下面代码示例中的名称相匹配。
一个top-level程序(grandparent.py):
- 使用subprocess.run或subprocess.Popen调用程序(parent.py)
- 检查 parent.py 中的 return 值是否正常。
- 从主进程收集 stdout 和 stderr 'parent.py'。
- 不想等待孙子完成。
调用的程序(parent.py)
- 可能会先做一些事情。
- 生成一个非常长的进程(孙进程 - 下面代码中的“longProcess”)。
- 可能会做更多的工作。
- Returns 它的结果并退出,而孙子 (longProcess) 继续做它所做的事情。
解决方案概要
重要的部分不是 subprocess
发生的事情。相反,创建 grandchild/longProcess 的方法是关键部分。要保证孙子真正解脱parent.py.
- 子进程只需要以捕获输出的方式使用。
- longProcess(孙子)需要发生以下情况:
- 应该使用多处理启动。
- 需要将多处理的 'daemon' 设置为 False。
- 它也应该使用 double-fork 过程调用。
- 在double-fork中,需要做额外的工作来确保进程真正独立于parent.py。具体来说:
- 将执行从 parent.py 的环境中移开。
- 使用文件处理确保孙子不再使用从 parent.py.
继承的文件句柄(stdin、stdout、stderr)
示例代码
grandparent.py - 使用 subprocess.run()
调用 parent.py
#!/usr/bin/env python3
import subprocess
p = subprocess.run(["/usr/bin/python3", "/path/to/parent.py"], capture_output=True)
## Comment the following if you don't need reassurance
print("The return code is: " + str(p.returncode))
print("The standard out is: ")
print(p.stdout)
print("The standard error is: ")
print(p.stderr)
parent.py - 启动 longProcess/grandchild 并退出,留下孙 运行。 10 秒后,孙子会将计时信息写入 /tmp/timelog
。
!/usr/bin/env python3
import time
def longProcess() :
time.sleep(10)
fo = open("/tmp/timelog", "w")
fo.write("I slept! The time now is: " + time.asctime(time.localtime()) + "\n")
fo.close()
import os,sys
def spawnDaemon(func):
# do the UNIX double-fork magic, see Stevens' "Advanced
# Programming in the UNIX Environment" for details (ISBN 0201563177)
try:
pid = os.fork()
if pid > 0: # parent process
return
except OSError as e:
print("fork #1 failed. See next. " )
print(e)
sys.exit(1)
# Decouple from the parent environment.
os.chdir("/")
os.setsid()
os.umask(0)
# do second fork
try:
pid = os.fork()
if pid > 0:
# exit from second parent
sys.exit(0)
except OSError as e:
print("fork #2 failed. See next. " )
print(e)
print(1)
# Redirect standard file descriptors.
# Here, they are reassigned to /dev/null, but they could go elsewhere.
sys.stdout.flush()
sys.stderr.flush()
si = open('/dev/null', 'r')
so = open('/dev/null', 'a+')
se = open('/dev/null', 'a+')
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
# Run your daemon
func()
# Ensure that the daemon exits when complete
os._exit(os.EX_OK)
import multiprocessing
daemonicGrandchild=multiprocessing.Process(target=spawnDaemon, args=(longProcess,))
daemonicGrandchild.daemon=False
daemonicGrandchild.start()
print("have started the daemon") # This will get captured as stdout by grandparent.py
参考资料
以上代码主要受到以下两个资源的启发。
我有一个 python 脚本可以执行此操作:
p = subprocess.Popen(pythonscript.py, stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=False)
theStdin=request.input.encode('utf-8')
(outputhere,errorshere) = p.communicate(input=theStdin)
它按预期工作,它等待子进程通过 p.communicate() 完成。但是在 pythonscript.py 中,我想“触发并忘记”一个“孙子”进程。我目前正在通过覆盖连接函数来做到这一点:
class EverLastingProcess(Process):
def join(self, *args, **kwargs):
pass # Overwrites join so that it doesn't block. Otherwise parent waits.
def __del__(self):
pass
然后这样开始:
p = EverLastingProcess(target=nameOfMyFunction, args=(arg1, etc,), daemon=False)
p.start()
这也很好,我只是在 bash 终端或 bash 脚本中 运行 pythonscript.py。控制和响应 returns,而 EverLastingProcess 启动的子进程继续运行。但是,当我 运行 pythonscript.py 和 Popen 运行 进行如上所示的过程时,从时间上看,Popen 正在等待孙子完成。 我怎样才能让 Popen 只等待子进程,而不是任何孙进程?
编辑: 以下在 Python 升级后停止工作,请参阅 Lachele 接受的答案。
同事的工作答案,改为shell=True,像这样:
p = subprocess.Popen(pythonscript.py, stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=True)
我已经测试过,孙子进程在子进程 returns 之后保持活动状态,而无需等待它们完成。
当我们最近升级 Python 时,上面的解决方案(使用 join
方法和 shell=True
添加)停止工作。
互联网上有很多关于这部分内容的参考资料,但我花了一些时间才想出一个对整个问题有用的解决方案。
以下解决方案已在 Python 3.9.5 和 3.9.7 中测试。
问题概要
脚本的名称与下面代码示例中的名称相匹配。
一个top-level程序(grandparent.py):
- 使用subprocess.run或subprocess.Popen调用程序(parent.py)
- 检查 parent.py 中的 return 值是否正常。
- 从主进程收集 stdout 和 stderr 'parent.py'。
- 不想等待孙子完成。
调用的程序(parent.py)
- 可能会先做一些事情。
- 生成一个非常长的进程(孙进程 - 下面代码中的“longProcess”)。
- 可能会做更多的工作。
- Returns 它的结果并退出,而孙子 (longProcess) 继续做它所做的事情。
解决方案概要
重要的部分不是 subprocess
发生的事情。相反,创建 grandchild/longProcess 的方法是关键部分。要保证孙子真正解脱parent.py.
- 子进程只需要以捕获输出的方式使用。
- longProcess(孙子)需要发生以下情况:
- 应该使用多处理启动。
- 需要将多处理的 'daemon' 设置为 False。
- 它也应该使用 double-fork 过程调用。
- 在double-fork中,需要做额外的工作来确保进程真正独立于parent.py。具体来说:
- 将执行从 parent.py 的环境中移开。
- 使用文件处理确保孙子不再使用从 parent.py. 继承的文件句柄(stdin、stdout、stderr)
示例代码
grandparent.py - 使用 subprocess.run()
#!/usr/bin/env python3
import subprocess
p = subprocess.run(["/usr/bin/python3", "/path/to/parent.py"], capture_output=True)
## Comment the following if you don't need reassurance
print("The return code is: " + str(p.returncode))
print("The standard out is: ")
print(p.stdout)
print("The standard error is: ")
print(p.stderr)
parent.py - 启动 longProcess/grandchild 并退出,留下孙 运行。 10 秒后,孙子会将计时信息写入 /tmp/timelog
。
!/usr/bin/env python3
import time
def longProcess() :
time.sleep(10)
fo = open("/tmp/timelog", "w")
fo.write("I slept! The time now is: " + time.asctime(time.localtime()) + "\n")
fo.close()
import os,sys
def spawnDaemon(func):
# do the UNIX double-fork magic, see Stevens' "Advanced
# Programming in the UNIX Environment" for details (ISBN 0201563177)
try:
pid = os.fork()
if pid > 0: # parent process
return
except OSError as e:
print("fork #1 failed. See next. " )
print(e)
sys.exit(1)
# Decouple from the parent environment.
os.chdir("/")
os.setsid()
os.umask(0)
# do second fork
try:
pid = os.fork()
if pid > 0:
# exit from second parent
sys.exit(0)
except OSError as e:
print("fork #2 failed. See next. " )
print(e)
print(1)
# Redirect standard file descriptors.
# Here, they are reassigned to /dev/null, but they could go elsewhere.
sys.stdout.flush()
sys.stderr.flush()
si = open('/dev/null', 'r')
so = open('/dev/null', 'a+')
se = open('/dev/null', 'a+')
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
# Run your daemon
func()
# Ensure that the daemon exits when complete
os._exit(os.EX_OK)
import multiprocessing
daemonicGrandchild=multiprocessing.Process(target=spawnDaemon, args=(longProcess,))
daemonicGrandchild.daemon=False
daemonicGrandchild.start()
print("have started the daemon") # This will get captured as stdout by grandparent.py
参考资料
以上代码主要受到以下两个资源的启发。