【问题标题】:Wait for the first subprocess to finish等待第一个子进程完成
【发布时间】:2020-06-19 10:10:12
【问题描述】:

我有一个subprocess' 进程列表。我不与他们交流,只是等待。

我想等待第一个过程完成(此解决方案有效):

import subprocess

a = subprocess.Popen(['...'])
b = subprocess.Popen(['...'])

# wait for the first process to finish
while True:
    over = False
    for child in {a, b}:
        try:
            rst = child.wait(timeout=5)
        except subprocess.TimeoutExpired:
            continue  # this subprocess is still running

        if rst is not None:  # subprocess is no more running
            over = True
            break  # If either subprocess exits, so do we.
    if over:
        break

我不想使用 os.wait(),因为它可能会从另一个 subprocess 返回,而不是我正在等待的列表的一部分。

一个漂亮而优雅的解决方案可能是使用epoll 或选择并且没有任何循环。

【问题讨论】:

  • 为什么不使用.communicate
  • 我认为您的解决方案非常优雅。您能否让进程在完成后(并且仅在完成后)向 stdout 或 stderr 写入内容?
  • 还有一个问题——你会考虑使用类似pypi.org/project/psutil的东西吗?
  • 子进程不写入 stdout 或 stderr ,我真的不想和他们交流。我不知道 psutil ,我会看看,或者你能用这个 lib 提出一个解决方案吗?谢谢
  • @raphaelauv - 请参阅下面的答案。显然,它们为这个用例提供了一个特定的功能。

标签: python subprocess wait


【解决方案1】:

可以为此使用os.wait()。你只需要循环调用它,直到它告诉你你关心的进程之一已经退出。

import subprocess

a = subprocess.Popen(['...'])

b = subprocess.Popen(['...'])


# wait for the first process to finish
watched_pids = set(proc.pid for proc in (a, b))
while True:
    pid, _ = os.wait()
    if pid in watched_pids:
        break

但是,os.wait() 的隐藏副作用是您丢失了进程的退出代码。在os.wait()完成后它只是None,如果你稍后调用proc.wait()proc.poll()proc.communicate()他们将找不到返回码,默认为0。可以自己设置,但它有点hacky。

def wait_and_handle_exitstatus(all_procs):
    pid, status = os.wait()
    for proc in all_procs:
        if proc.pid == pid:
            # We need to set the process's exit status now, or we
            # won't be able to retrieve it later and it will be
            # assumed to be 0.
            # This is a kind of hacky solution, but this function has existed
            # ever since subprocess was first included in the stdlib and is
            # still there in 3.10+, so it *should* be pretty stable.
            proc._handle_exitstatus(status)
    return pid, status

然后您可以使用第一个代码块,只需将os.wait() 替换为wait_and_handle_exitstatus(ALL_PROCS)。但是,您必须向wait_and_handle_exitstatus 传递可能正在运行的所有子进程(Popen 对象)的列表,并且您可能会关心它的返回代码,以便它可以找到该进程并设置其退出代码。

【讨论】:

    【解决方案2】:

    这是一个使用 psutil 的解决方案 - 正好针对这个用例:

    import subprocess
    import psutil
    
    a = subprocess.Popen(['/bin/sleep', "2"])
    
    b = subprocess.Popen(['/bin/sleep', "4"])
    
    procs_list = [psutil.Process(a.pid), psutil.Process(b.pid)]
    
    def on_terminate(proc):
         print("process {} terminated".format(proc))
    
    # waits for multiple processes to terminate
    gone, alive = psutil.wait_procs(procs_list, timeout=3, callback=on_terminate)
    

    或者,如果您希望有一个循环等待其中一个过程完成:

    while True: 
        gone, alive = psutil.wait_procs(procs_list, timeout=3, callback=on_terminate) 
        if len(gone)>0: 
            break
    

    【讨论】:

    • 这能回答你的问题吗?
    • 似乎在做这些事情,但文档并没有确保如果至少一个进程消失了,这个调用会返回这个函数是否可以返回一个空的消失结构?
    • 是的,可以。如果将超时更改为较小的数字(例如 1),您将获得一个空的“已消失”结构。如果这回答了你的问题,顺便说一句,如果你能接受我的回答,那就太好了。
    • 如果它回答了你的问题,如果你能接受就太好了。
    • 我的编辑被拒绝,但正确答案是:while True:gone, alive = psutil.wait_procs(procs_list, timeout=3, callback=on_terminate) if len(gone)>0: break
    【解决方案3】:

    如果您不需要从进程中获取输出,Popen.poll() 似乎是检查它们是否完成的最简单方法。下面的while True 循环纯粹用于演示目的:您可以决定如何在较大的程序中执行此操作(例如,在单独的线程中进行检查,在程序的其他工作之间进行检查等)。

    from subprocess import Popen
    from time import sleep
    
    ps = [
        Popen(['sleep', t])
        for t in ('3', '5', '2')
    ]
    
    while True:
        exit_codes = [p.poll() for p in ps]
        print(exit_codes)
        if any(ec is not None for ec in exit_codes):
            break
        else:
            sleep(1)
    

    演示输出:

    [None, None, None]
    [None, None, None]
    [None, None, 0]
    

    【讨论】:

    • Poll 有一个问题。在某些情况下,它会返回错误的结果。
    • @Roy2012 很高兴知道。这个问题是否记录在某处?
    • 是的 - 但我懒得找到它:) 这里有一个链接:lists.gt.net/python/bugs/633489
    • 我的编辑被拒绝了,但问题的正确答案是如果有的话(ec is not None for ec in exit_codes):break else: sleep(1)
    • 问题是:等待第一个子进程完成,而不是全部。这就是为什么我想纠正你的答案。
    【解决方案4】:

    几乎有两种方法可以做到这一点,如果您希望命令阻塞并暂停您的程序直到它完成,请使用subprocess.call

    a = subprocess.call('...')
    b = subprocess.call('...')
    

    我不认为这是你想要的。

    如果您不希望他们停止整个程序,并且只需要在调用另一个之前检查其中一个是否完成,您应该使用.communicate

    ```py
    a = subprocess.Popen(['...'])
    
    b = subprocess.Popen(['...'])
    
    ....
        for child in {a, b}:
            try:
                result, err = child.communicate(timeout=5)
    

    .communicate 几乎是最优雅、最简单和推荐的解决方案

    【讨论】:

    • 感谢您的回答,但我更喜欢非活动等待(没有时间:True)另外我不想与子进程通信
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-25
    • 1970-01-01
    • 2010-11-06
    相关资源
    最近更新 更多