我怎么能等待子进程?

时间:2014-03-19 17:49:48

标签: python linux

我有一个Python脚本可以启动这样的任务:

import os
os.system("./a.sh")
do_c()

但是a.sh是一个启动其他程序的bash脚本。在启动所有脚本之前,bash脚本本身似乎已准备就绪。

如何在执行do_c()之前等待所有脚本(子进程)准备就绪?

澄清:当我准备好时,我的意思是完成/退出。

实施例

run.py

此文件可以更改。但不要依赖睡眠,因为我不知道a.pyb.py需要多长时间。

#!/usr/bin/env python

import os
from time import sleep

print("Started run.py")
os.system("./a.py")
print("a is ready.")
print("Now all messages should be there.")

sleep(30)

a.py

这可能不会被修改:

#!/usr/bin/env python

import subprocess
import sys

print("  Started a.py")
pid = subprocess.Popen([sys.executable, "b.py"])
print("  End of a.py")

b.py

这可能不会被修改:

#!/usr/bin/env python

from time import sleep

print("    Started b.py")
sleep(10)
print("    Ended b.py")

期望的输出

最后一条消息必须是Now all messages should be there.

当前输出

started run.py
  Started a.py
  End of a.py
a is ready.
Now all messages should be there.
    Started b.py
    Ended b.py

1 个答案:

答案 0 :(得分:5)

处理这种情况的常用方法不起作用。等待a.py(默认情况下os.system执行此操作)不起作用,因为a.py在其子项执行完毕之前退出。查找b.py的PID非常棘手,因为一旦a.py退出,b.py就无法再以任何方式连接到它 - 即使b.py的父PID为1, init进程。

然而,可以利用继承的文件描述符作为一个孩子死亡的穷人信号。设置一个管道,其读取结束位于run.py,其写入结束由a.py及其所有子项继承。只有当最后一个子项退出时才会关闭管道的写入端,并且管道读取端的read()将停止阻塞。

以下是run.py的修改版本,它实现了这个想法,显示了所需的输出:

#!/usr/bin/env python

import os
from time import sleep

print("Started run.py")

r, w = os.pipe()
pid = os.fork()
if pid == 0:
    os.close(r)
    os.execlp("./a.py", "./a.py")
    os._exit(127)   # unreached unless execlp fails
os.close(w)
os.waitpid(pid, 0)  # wait for a.py to finish
print("a is ready.")

os.read(r, 1)       # wait for all the children that inherited `w` to finish
os.close(r)
print("Now all messages should be there.")

<强>解释

Pipe是一个进程间通信设备,允许父进程和子进程通过继承的文件描述符进行通信。通常,一个人创建一个管道,分叉一个进程,可能执行一个外部文件,并从管道的读取端读取一些数据,将另一个数据写入管道的写入端。 (Shell使用这种机制实现管道,更进一步,使标准文件描述符如stdin和stdout指向管道的适当端。)

在这种情况下,我们并不关心与孩子交换实际数据,我们只希望在他们退出时收到通知。为了实现这一点,我们利用了这样一个事实:当一个进程终止时,内核会关闭它的所有文件描述符。反过来,当分叉进程继承文件描述符时,如果关闭描述符的所有副本,则认为文件描述符是关闭的。因此,我们设置了一个带有写端的管道,该管道将由a.py生成的所有进程继承。这些过程不需要知道关于这个文件描述符的任何信息,唯一重要的是当它们全部死亡时,管道的写入端将关闭。这将在管道的读取端指示os.read()不再阻塞并返回一个0长度的字符串,该字符串表示文件结束条件。

代码是这个想法的简单实现:

  • os.pipe()与第一个print之间的部分是os.system()的实现,区别在于它关闭了子项中管道的读取端。 (这是必要的 - 简单地调用os.system()会使读取端保持打开状态,这会阻止父进程中的最终读取正常工作。)

  • os.fork()复制当前进程,唯一的方法是区分父级和子级,在父级中获得子级PID(并且子级获得0) ,因为它始终可以使用os.getpid())找到它的PID。

  • if pid == 0:分支在子项中运行,只执行./a.py。 &#34; Exec的&#34;表示它运行指定的可执行文件而不返回os._exit()仅在execlp失败的情况下出现(在Python中可能是不必要的,因为execlp的失败会引发异常而退出程序,但仍然如此)。程序的其余部分在父程序中运行。

  • 父关闭管道的写端(否则尝试从读端读取将导致死锁)。 os.waitpid(pid)正在等待a.py正常执行的os.system()。在我们的情况下,没有必要拨打waitpid,但这样做是个好主意,以防止僵尸留下来。

  • os.read(r, 1)是神奇发生的地方:它试图从管道的读取端读取最多1个字符。由于没有人写入管道的写入端,因此读取将阻塞,直到管道的写入端关闭。由于a.py的子节点对继承的文件描述符一无所知,因此关闭它的唯一方法是由内核在各个进程死亡后执行它。当所有继承的写端描述符都关闭时,os.read()返回一个零长度字符串,我们忽略该字符串并继续执行。

  • 最后,我们关闭管道的写入端,以便释放共享资源。