Python子进程返回代码而无需等待

时间:2014-09-10 18:52:26

标签: python multithreading multiprocessing subprocess

我的问题很有希望与我读过的任何其他内容无关。我想使用子进程和多处理串行生成一堆作业并将返回代码返回给我。问题是我不想等待()所以我可以一次生成所有作业,但我确实想知道什么时候它完成所以我可以得到返回代码。我有这个奇怪的问题,如果我poll()过程它不会运行。它只是在活动监视器中挂起而没有运行(我在Mac上)。我以为我可以使用一个观察者线程,但我挂在q_out.get()上,这让我相信可能我正在填补缓冲区和死锁。我不知道怎么解决这个问题。这基本上就是我的代码。如果有人对如何做到这一点有任何更好的想法,我会很乐意彻底改变我的方法。

def watchJob(p1,out_q):
    while p1.poll() == None:
        pass
    print "Job is done"
    out_q.put(p1.returncode)

def runJob(out_q):
    LOGFILE = open('job_to_run.log','w')
    p1 = Popen(['../../bin/jobexe','job_to_run'], stdout = LOGFILE)
    t = threading.Thread(target=watchJob, args=(p1,out_q))
    t.start()

out_q= Queue()
outlst=[]
for i in range(len(nprocs)):
    proc = Process(target=runJob, args=(out_q,))
    proc.start()
    outlst.append(out_q.get()) # This hangs indefinitely
    proc.join()

2 个答案:

答案 0 :(得分:2)

您既不需要多处理也不需要线程。您可以并行运行多个子进程,并在一个线程中收集它们的法规:

#!/usr/bin/env python3
from subprocess import Popen

def run(cmd, log_filename):
    with open(log_filename, 'wb', 0) as logfile:
        return Popen(cmd, stdout=logfile)

# start several subprocesses
processes = {run(['echo', c], 'subprocess.%s.log' % c) for c in 'abc'}
# now they all run in parallel
# report as soon as a child process exits
while processes: 
    for p in processes: 
        if p.poll() is not None:
           processes.remove(p) 
           print('{} done, status {}'.format(p.args, p.returncode))
           break

p.argscmd存储在Python 3.3+中,在早期的Python版本中自行跟踪cmd

另见:

要限制并行作业的数量,可以使用ThreadPool(如the first link所示):

#!/usr/bin/env python3
from multiprocessing.dummy import Pool # use threads
from subprocess import Popen

def run_until_done(args):
    cmd, log_filename = args
    try:
        with open(log_filename, 'wb', 0) as logfile:
            p = Popen(cmd, stdout=logfile)
        return cmd, p.wait(), None
    except Exception as e:
        return cmd, None, str(e)

commands = ((('echo', str(d)), 'subprocess.%03d.log' % d) for d in range(500))
pool = Pool(128) # 128 concurrent commands at a time
for cmd, status, error in pool.imap_unordered(run_until_done, commands):
    if error is None:
       fmt = '{cmd} done, status {status}'
    else:
       fmt = 'failed to run {cmd}, reason: {error}'
    print(fmt.format_map(vars())) # or fmt.format(**vars()) on older versions

示例中的线程池有128个线程(不多也不少)。它不能同时执行超过128个作业。只要任何线程释放(完成一个作业),它就会占用另一个,等等。并发执行的作业总数受线程数限制。新工作不会等待所有128个以前的工作完成。它是在任何旧作业完成时启动的。

答案 1 :(得分:1)

如果您要在一个帖子中运行watchJob,则没有理由忙于p1.poll;只需调用p1.wait()即可阻止该流程完成。使用busy循环需要不断释放/重新获取GIL,这会降低主线程的速度,并且还会占用CPU,这会进一步损害性能。

此外,如果您未使用子进程的stdout,则不应将其发送到PIPE,因为如果进程写得足够,可能会导致死锁数据到stdout缓冲区以填充它(实际上可能是你的情况下发生的事情)。这里也没有必要使用multiprocessing;只需在主线程中调用Popen,然后让watchJob线程等待进程完成。

import threading
from subprocess import Popen
from Queue import Queue

def watchJob(p1, out_q):
    p1.wait()
    out_q.put(p1.returncode)

out_q = Queue()
outlst=[]
p1 = Popen(['../../bin/jobexe','job_to_run'])
t = threading.Thread(target=watchJob, args=(p1,out_q))
t.start()
outlst.append(out_q.get())
t.join()

修改

以下是如何以这种方式同时运行多个作业:

out_q = Queue()
outlst = []
threads = []
num_jobs = 3
for _ in range(num_jobs):
    p = Popen(['../../bin/jobexe','job_to_run'])
    t = threading.Thread(target=watchJob, args=(p1, out_q))
    t.start()
    # Don't consume from the queue yet.

# All jobs are running, so now we can start
# consuming results from the queue.
for _ in range(num_jobs):
    outlst.append(out_q.get())
    t.join()