代码之家  ›  专栏  ›  技术社区  ›  Jose Javier Gonzalez Ortiz

Python子进程返回错误的退出代码

  •  5
  • Jose Javier Gonzalez Ortiz  · 技术社区  · 6 年前

    我编写了一个脚本来启动一些并行运行的进程(简单的单元测试)。可以了 N 具有的作业 num_workers 一次并行处理。

    我的第一个实现以批处理的方式运行流程 num\u工人 而且似乎工作得很好(我使用了 false 命令测试行为)

    import subprocess
    
    errors = 0
    num_workers = 10
    N = 100
    i = 0
    
    while i < N:
        processes = []
        for j in range(i, min(i+num_workers, N)):
            p = subprocess.Popen(['false'])
            processes.append(p)
    
        [p.wait() for p in processes]
        exit_codes = [p.returncode for p in processes]
    
        errors += sum(int(e != 0) for e in exit_codes)
        i += num_workers
    
    print(f"There were {errors}/{N} errors")
    

    然而,这些测试花费的时间并不相等,所以我有时会等待一个缓慢的测试完成。因此,我重写了它,以便在任务完成时继续分配任务

    import subprocess
    import os
    
    
    errors = 0
    num_workers = 40
    N = 100
    assigned = 0
    completed = 0
    processes = set()
    
    while completed < N:
        if assigned < N:
            p = subprocess.Popen(['false'])
            processes.add((assigned, p))
            assigned += 1
        if len(processes) >= num_workers or assigned == N:
            os.wait()
    
        for i, p in frozenset(processes):
            if p.poll() is not None:
                completed += 1
                processes.remove((i, p))
                err = p.returncode
                print(i, err)
                if err != 0:
                    errors += 1
    
    print(f"There were {errors}/{N} errors")
    

    然而,这会在最后几个过程中产生错误的结果。例如,在上面的示例中,它产生98/100个错误,而不是100个错误。我检查了,这与并发无关;出于某种原因,返回了2个最新作业,退出代码为0。

    为什么会发生这种情况?

    1 回复  |  直到 6 年前
        1
  •  1
  •   Nathan Vērzemnieks    6 年前

    问题在于 os.wait() . 它不仅等待子进程退出:它还返回该子进程的pid和“退出状态指示”,如下所示 the documentation 说。这需要等待子进程终止;但一旦子级终止,其返回代码就不再适用于 poll . 这里有一个简单的测试来重现这个问题:

    false\u runner。py公司

    import os
    import subprocess
    p = subprocess.Popen(['false'], stderr=subprocess.DEVNULL)
    pid, retcode = os.wait()
    print("From os.wait: {}".format(retcode))
    print("From popen object before poll: {}".format(p.returncode))
    p.poll()
    print("From popen object after poll: {}".format(p.returncode))
    

    输出

    njv@organon:~/tmp$ python false_runner.py
    From os.wait: 256
    From Popen object before poll: None
    From Popen object after poll: 0
    

    The source code for _internal_poll , called by Popen.poll ,表明这里发生了什么:什么时候 Popen 尝试呼叫 _waitpid 在其子进程的pid上 ChildProcessError: [Errno 10] No child processes ,并为自己分配 returncode ,因为此时无法确定子进程的返回代码。

    仅在示例中的最后两个子流程中发生这种情况的原因是 os.wait 只是被要求 or assigned == N 因为您的子流程太快了,所以只能执行一次或两次。如果你把它慢一点,你会得到更多的随机行为。

    至于修复方法:我可能会更换 操作系统。等待() 睡一觉。