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

Python子进程返回错误的退出代码,python,subprocess,exit-code,Python,Subprocess,Exit Code,我编写了一个脚本来启动一些并行运行的进程(简单的单元测试)。它将一次执行N作业,同时执行num_个工人并行进程 我的第一个实现以num_workers的批处理方式运行进程,并且似乎工作正常(我在这里使用false命令来测试该行为) 导入子流程 错误=0 工人数量=10 N=100 i=0 而i

我编写了一个脚本来启动一些并行运行的进程(简单的单元测试)。它将一次执行
N
作业,同时执行
num_个工人
并行进程

我的第一个实现以
num_workers
的批处理方式运行进程,并且似乎工作正常(我在这里使用
false
命令来测试该行为)

导入子流程
错误=0
工人数量=10
N=100
i=0
而i
然而,这些测试花费的时间并不相等,所以我有时会等待一个缓慢的测试完成。因此,我重写了它,以便在任务完成时继续分配任务

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")
导入子流程
导入操作系统
错误=0
工人数量=40
N=100
已分配=0
已完成=0
进程=集合()
完成时=num_工人或分配==N:
等等
对于冻结集中的i,p(进程):
如果p.poll()不是无:
已完成+=1
进程。删除((i,p))
err=p.returncode
打印(i,err)
如果出错!=0:
错误+=1
打印(f“有{errors}/{N}个错误”)
但是,这会在最后几个过程中产生错误的结果。例如,在上面的示例中,它产生98/100错误,而不是100错误。我检查了,这与并发无关;出于某种原因,最近的2个作业返回时退出代码为0


为什么会发生这种情况?

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

false_runner.py 输出 ,明确了这里发生的情况:当
Popen
尝试调用其子进程的pid上的
\u waitpid
时,它会得到
ChildProcessError:[Errno 10]没有子进程
,并为自己分配一个
returncode
值0,因为此时无法确定子进程的返回代码

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


至于解决方法:我可能会用睡眠取代
os.wait()

考虑使用而不是管理自己的并行进程。@ndmeiri什么竞争条件?只有主线程读取/写入
errors
变量,而不是争用条件
os.wait()
clobbers
poll()的返回代码
@NathanVērzemnieks好眼力!
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")
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