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()
clobberspoll()的返回代码
@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